refactor: remove uneeded helper in reservation code
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 70f511e0878c6d16944f046dde834c73d2fdc4b7..7eef4100355d0113f23848f72bac8c26c9b38acc 100644 (file)
@@ -1,39 +1,68 @@
 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
-import crypto from 'node:crypto';
-import fs from 'node:fs';
-import path from 'node:path';
+import { createHash } from 'node:crypto';
+import {
+  type FSWatcher,
+  closeSync,
+  existsSync,
+  mkdirSync,
+  openSync,
+  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 WebSocket, { type RawData } from 'ws';
+import { type RawData, WebSocket } from 'ws';
 
+import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator';
+import { ChargingStationWorkerBroadcastChannel } from './broadcast-channel/ChargingStationWorkerBroadcastChannel';
+import {
+  addConfigurationKey,
+  deleteConfigurationKey,
+  getConfigurationKey,
+  setConfigurationKeyValue,
+} from './ConfigurationKeyUtils';
 import {
-  AutomaticTransactionGenerator,
-  ChargingStationConfigurationUtils,
-  ChargingStationUtils,
-  ChargingStationWorkerBroadcastChannel,
-  IdTagsCache,
-  MessageChannelUtils,
-  SharedLRUCache,
-} from './internal';
+  buildConnectorsMap,
+  checkConnectorsConfiguration,
+  checkStationInfoConnectorStatus,
+  checkTemplate,
+  countReservableConnectors,
+  createBootNotificationRequest,
+  createSerialNumber,
+  getAmperageLimitationUnitDivider,
+  getBootConnectorStatus,
+  getChargingStationConnectorChargingProfilesPowerLimit,
+  getChargingStationId,
+  getDefaultVoltageOut,
+  getHashId,
+  getIdTagsFile,
+  getMaxNumberOfEvses,
+  getPhaseRotationValue,
+  hasFeatureProfile,
+  initializeConnectorsMapStatus,
+  propagateSerialNumber,
+  stationTemplateToStationInfo,
+  warnTemplateKeysDeprecation,
+} from './Helpers';
+import { IdTagsCache } from './IdTagsCache';
 import {
-  // OCPP16IncomingRequestService,
+  OCPP16IncomingRequestService,
   OCPP16RequestService,
-  // OCPP16ResponseService,
+  OCPP16ResponseService,
   OCPP16ServiceUtils,
   OCPP20IncomingRequestService,
   OCPP20RequestService,
-  // OCPP20ResponseService,
+  OCPP20ResponseService,
   type OCPPIncomingRequestService,
   type OCPPRequestService,
-  // OCPPServiceUtils,
+  OCPPServiceUtils,
 } from './ocpp';
-import { OCPP16IncomingRequestService } from './ocpp/1.6/OCPP16IncomingRequestService';
-import { OCPP16ResponseService } from './ocpp/1.6/OCPP16ResponseService';
-import { OCPP20ResponseService } from './ocpp/2.0/OCPP20ResponseService';
-import { OCPPServiceUtils } from './ocpp/OCPPServiceUtils';
+import { SharedLRUCache } from './SharedLRUCache';
 import { BaseError, OCPPError } from '../exception';
 import { PerformanceStatistics } from '../performance';
 import {
@@ -46,7 +75,6 @@ import {
   type ChargingStationInfo,
   type ChargingStationOcppConfiguration,
   type ChargingStationTemplate,
-  ConnectorPhaseRotation,
   type ConnectorStatus,
   ConnectorStatusEnum,
   CurrentType,
@@ -75,8 +103,12 @@ import {
   PowerUnits,
   RegistrationStatusEnumType,
   RequestCommand,
+  type Reservation,
+  type ReservationFilterKey,
+  ReservationTerminationReason,
   type Response,
   StandardParametersKey,
+  type Status,
   type StatusNotificationRequest,
   type StatusNotificationResponse,
   StopTransactionReason,
@@ -91,12 +123,36 @@ import {
 } from '../types';
 import {
   ACElectricUtils,
+  AsyncLock,
+  AsyncLockType,
   Configuration,
   Constants,
   DCElectricUtils,
-  FileUtils,
-  Utils,
+  buildChargingStationAutomaticTransactionGeneratorConfiguration,
+  buildConnectorsStatus,
+  buildEvsesStatus,
+  buildStartedMessage,
+  buildStoppedMessage,
+  buildUpdatedMessage,
+  cloneObject,
+  convertToBoolean,
+  convertToInt,
+  exponentialDelay,
+  formatDurationMilliSeconds,
+  formatDurationSeconds,
+  getRandomInteger,
+  getWebSocketCloseEventStatusString,
+  handleFileException,
+  isNotEmptyArray,
+  isNotEmptyString,
+  isNullOrUndefined,
+  isUndefined,
+  logPrefix,
   logger,
+  roundTo,
+  secureRandom,
+  sleep,
+  watchJsonFile,
 } from '../utils';
 
 export class ChargingStation {
@@ -113,7 +169,7 @@ export class ChargingStation {
   public readonly evses: Map<number, EvseStatus>;
   public readonly requests: Map<string, CachedRequest>;
   public performanceStatistics!: PerformanceStatistics | undefined;
-  public heartbeatSetInterval!: NodeJS.Timeout;
+  public heartbeatSetInterval?: NodeJS.Timeout;
   public ocppRequestService!: OCPPRequestService;
   public bootNotificationRequest!: BootNotificationRequest;
   public bootNotificationResponse!: BootNotificationResponse | undefined;
@@ -123,15 +179,18 @@ export class ChargingStation {
   private configurationFileHash!: string;
   private connectorsConfigurationHash!: string;
   private evsesConfigurationHash!: string;
+  private automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration;
   private ocppIncomingRequestService!: OCPPIncomingRequestService;
   private readonly messageBuffer: Set<string>;
   private configuredSupervisionUrl!: URL;
   private wsConnectionRestarted: boolean;
   private autoReconnectRetryCount: number;
-  private templateFileWatcher!: fs.FSWatcher | undefined;
+  private templateFileWatcher!: FSWatcher | undefined;
+  private templateFileHash!: string;
   private readonly sharedLRUCache: SharedLRUCache;
-  private webSocketPingSetInterval!: NodeJS.Timeout;
+  private webSocketPingSetInterval?: NodeJS.Timeout;
   private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
+  private reservationExpirationSetInterval?: NodeJS.Timeout;
 
   constructor(index: number, templateFile: string) {
     this.started = false;
@@ -152,38 +211,35 @@ export class ChargingStation {
     this.initialize();
   }
 
+  public get hasEvses(): boolean {
+    return this.connectors.size === 0 && this.evses.size > 0;
+  }
+
   private get wsConnectionUrl(): URL {
     return new URL(
       `${
         this.getSupervisionUrlOcppConfiguration() &&
-        Utils.isNotEmptyString(this.getSupervisionUrlOcppKey())
-          ? ChargingStationConfigurationUtils.getConfigurationKey(
-              this,
-              this.getSupervisionUrlOcppKey()
-            )?.value
+        isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
+        isNotEmptyString(getConfigurationKey(this, this.getSupervisionUrlOcppKey())?.value)
+          ? getConfigurationKey(this, this.getSupervisionUrlOcppKey())!.value
           : this.configuredSupervisionUrl.href
-      }/${this.stationInfo.chargingStationId}`
+      }/${this.stationInfo.chargingStationId}`,
     );
   }
 
-  private get hasEvses(): boolean {
-    return this.connectors.size === 0 && this.evses.size > 0;
-  }
-
   public logPrefix = (): string => {
-    return Utils.logPrefix(
+    return logPrefix(
       ` ${
-        (Utils.isNotEmptyString(this?.stationInfo?.chargingStationId)
+        (isNotEmptyString(this?.stationInfo?.chargingStationId)
           ? this?.stationInfo?.chargingStationId
-          : ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile())) ??
+          : getChargingStationId(this.index, this.getTemplateFromFile()!)) ??
         'Error at building log prefix'
-      } |`
+      } |`,
     );
   };
 
   public hasIdTags(): boolean {
-    const idTagsFile = ChargingStationUtils.getIdTagsFile(this.stationInfo);
-    return Utils.isNotEmptyArray(this.idTagsCache.getIdTags(idTagsFile));
+    return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo)!));
   }
 
   public getEnableStatistics(): boolean {
@@ -194,17 +250,11 @@ export class ChargingStation {
     return this.stationInfo.mustAuthorizeAtRemoteStart ?? true;
   }
 
-  public getPayloadSchemaValidation(): boolean {
-    return this.stationInfo.payloadSchemaValidation ?? true;
-  }
-
-  public getNumberOfPhases(stationInfo?: ChargingStationInfo): number | undefined {
+  public getNumberOfPhases(stationInfo?: ChargingStationInfo): number {
     const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
     switch (this.getCurrentOutType(stationInfo)) {
       case CurrentType.AC:
-        return !Utils.isUndefined(localStationInfo.numberOfPhases)
-          ? localStationInfo.numberOfPhases
-          : 3;
+        return !isUndefined(localStationInfo.numberOfPhases) ? localStationInfo.numberOfPhases! : 3;
       case CurrentType.DC:
         return 0;
     }
@@ -218,26 +268,26 @@ export class ChargingStation {
     return this?.bootNotificationResponse?.status;
   }
 
-  public isInUnknownState(): boolean {
-    return Utils.isNullOrUndefined(this?.bootNotificationResponse?.status);
+  public inUnknownState(): boolean {
+    return isNullOrUndefined(this?.bootNotificationResponse?.status);
   }
 
-  public isInPendingState(): boolean {
+  public inPendingState(): boolean {
     return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING;
   }
 
-  public isInAcceptedState(): boolean {
+  public inAcceptedState(): boolean {
     return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED;
   }
 
-  public isInRejectedState(): boolean {
+  public inRejectedState(): boolean {
     return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED;
   }
 
   public isRegistered(): boolean {
     return (
-      this.isInUnknownState() === false &&
-      (this.isInAcceptedState() === true || this.isInPendingState() === true)
+      this.inUnknownState() === false &&
+      (this.inAcceptedState() === true || this.inPendingState() === true)
     );
   }
 
@@ -245,6 +295,18 @@ export class ChargingStation {
     return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative;
   }
 
+  public hasConnector(connectorId: number): boolean {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        if (evseStatus.connectors.has(connectorId)) {
+          return true;
+        }
+      }
+      return false;
+    }
+    return this.connectors.has(connectorId);
+  }
+
   public isConnectorAvailable(connectorId: number): boolean {
     return (
       connectorId > 0 &&
@@ -256,10 +318,9 @@ export class ChargingStation {
     if (this.hasEvses) {
       let numberOfConnectors = 0;
       for (const [evseId, evseStatus] of this.evses) {
-        if (evseId === 0) {
-          continue;
+        if (evseId > 0) {
+          numberOfConnectors += evseStatus.connectors.size;
         }
-        numberOfConnectors += evseStatus.connectors.size;
       }
       return numberOfConnectors;
     }
@@ -277,6 +338,7 @@ export class ChargingStation {
           return evseStatus.connectors.get(connectorId);
         }
       }
+      return undefined;
     }
     return this.connectors.get(connectorId);
   }
@@ -286,51 +348,53 @@ export class ChargingStation {
   }
 
   public getOcppStrictCompliance(): boolean {
-    return this.stationInfo?.ocppStrictCompliance ?? false;
+    return this.stationInfo?.ocppStrictCompliance ?? true;
   }
 
-  public getVoltageOut(stationInfo?: ChargingStationInfo): number | undefined {
-    const defaultVoltageOut = ChargingStationUtils.getDefaultVoltageOut(
+  public getVoltageOut(stationInfo?: ChargingStationInfo): number {
+    const defaultVoltageOut = getDefaultVoltageOut(
       this.getCurrentOutType(stationInfo),
+      this.logPrefix(),
       this.templateFile,
-      this.logPrefix()
     );
-    const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
-    return !Utils.isUndefined(localStationInfo.voltageOut)
-      ? localStationInfo.voltageOut
-      : defaultVoltageOut;
+    return (stationInfo ?? this.stationInfo).voltageOut ?? defaultVoltageOut;
   }
 
   public getMaximumPower(stationInfo?: ChargingStationInfo): number {
     const localStationInfo = stationInfo ?? this.stationInfo;
-    return (localStationInfo['maxPower'] as number) ?? localStationInfo.maximumPower;
+    // 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;
+    let connectorAmperageLimitationPowerLimit: number | undefined;
     if (
-      !Utils.isNullOrUndefined(this.getAmperageLimitation()) &&
-      this.getAmperageLimitation() < this.stationInfo?.maximumAmperage
+      !isNullOrUndefined(this.getAmperageLimitation()) &&
+      this.getAmperageLimitation()! < this.stationInfo.maximumAmperage!
     ) {
       connectorAmperageLimitationPowerLimit =
         (this.getCurrentOutType() === CurrentType.AC
           ? ACElectricUtils.powerTotal(
               this.getNumberOfPhases(),
               this.getVoltageOut(),
-              this.getAmperageLimitation() * this.getNumberOfConnectors()
+              this.getAmperageLimitation()! *
+                (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
             )
-          : DCElectricUtils.power(this.getVoltageOut(), this.getAmperageLimitation())) /
+          : DCElectricUtils.power(this.getVoltageOut(), this.getAmperageLimitation()!)) /
         this.powerDivider;
     }
     const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
     const connectorChargingProfilesPowerLimit =
-      ChargingStationUtils.getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId);
+      getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId);
     return Math.min(
       isNaN(connectorMaximumPower) ? Infinity : connectorMaximumPower,
-      isNaN(connectorAmperageLimitationPowerLimit)
+      isNaN(connectorAmperageLimitationPowerLimit!)
         ? Infinity
-        : connectorAmperageLimitationPowerLimit,
-      isNaN(connectorChargingProfilesPowerLimit) ? Infinity : connectorChargingProfilesPowerLimit
+        : connectorAmperageLimitationPowerLimit!,
+      isNaN(connectorChargingProfilesPowerLimit!) ? Infinity : connectorChargingProfilesPowerLimit!,
     );
   }
 
@@ -345,16 +409,36 @@ export class ChargingStation {
       }
     } else {
       for (const connectorId of this.connectors.keys()) {
-        if (
-          connectorId > 0 &&
-          this.getConnectorStatus(connectorId)?.transactionId === transactionId
-        ) {
+        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
           return this.getConnectorStatus(connectorId)?.transactionIdTag;
         }
       }
     }
   }
 
+  public getNumberOfRunningTransactions(): number {
+    let trxCount = 0;
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId === 0) {
+          continue;
+        }
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.transactionStarted === true) {
+            ++trxCount;
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+          ++trxCount;
+        }
+      }
+    }
+    return trxCount;
+  }
+
   public getOutOfOrderEndMeterValues(): boolean {
     return this.stationInfo?.outOfOrderEndMeterValues ?? false;
   }
@@ -394,10 +478,7 @@ export class ChargingStation {
       }
     } else {
       for (const connectorId of this.connectors.keys()) {
-        if (
-          connectorId > 0 &&
-          this.getConnectorStatus(connectorId)?.transactionId === transactionId
-        ) {
+        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
           return connectorId;
         }
       }
@@ -406,56 +487,48 @@ export class ChargingStation {
 
   public getEnergyActiveImportRegisterByTransactionId(
     transactionId: number,
-    rounded = false
+    rounded = false,
   ): number {
     return this.getEnergyActiveImportRegister(
-      this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)),
-      rounded
+      this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)!)!,
+      rounded,
     );
   }
 
   public getEnergyActiveImportRegisterByConnectorId(connectorId: number, rounded = false): number {
-    return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId), rounded);
+    return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId)!, rounded);
   }
 
   public getAuthorizeRemoteTxRequests(): boolean {
-    const authorizeRemoteTxRequests = ChargingStationConfigurationUtils.getConfigurationKey(
+    const authorizeRemoteTxRequests = getConfigurationKey(
       this,
-      StandardParametersKey.AuthorizeRemoteTxRequests
+      StandardParametersKey.AuthorizeRemoteTxRequests,
     );
-    return authorizeRemoteTxRequests
-      ? Utils.convertToBoolean(authorizeRemoteTxRequests.value)
-      : false;
+    return authorizeRemoteTxRequests ? convertToBoolean(authorizeRemoteTxRequests.value) : false;
   }
 
   public getLocalAuthListEnabled(): boolean {
-    const localAuthListEnabled = ChargingStationConfigurationUtils.getConfigurationKey(
+    const localAuthListEnabled = getConfigurationKey(
       this,
-      StandardParametersKey.LocalAuthListEnabled
+      StandardParametersKey.LocalAuthListEnabled,
     );
-    return localAuthListEnabled ? Utils.convertToBoolean(localAuthListEnabled.value) : false;
+    return localAuthListEnabled ? convertToBoolean(localAuthListEnabled.value) : false;
   }
 
   public getHeartbeatInterval(): number {
-    const HeartbeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
-      this,
-      StandardParametersKey.HeartbeatInterval
-    );
+    const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval);
     if (HeartbeatInterval) {
-      return Utils.convertToInt(HeartbeatInterval.value) * 1000;
+      return secondsToMilliseconds(convertToInt(HeartbeatInterval.value));
     }
-    const HeartBeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
-      this,
-      StandardParametersKey.HeartBeatInterval
-    );
+    const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval);
     if (HeartBeatInterval) {
-      return Utils.convertToInt(HeartBeatInterval.value) * 1000;
+      return secondsToMilliseconds(convertToInt(HeartBeatInterval.value));
     }
     this.stationInfo?.autoRegister === false &&
       logger.warn(
         `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${
           Constants.DEFAULT_HEARTBEAT_INTERVAL
-        }`
+        }`,
       );
     return Constants.DEFAULT_HEARTBEAT_INTERVAL;
   }
@@ -463,13 +536,9 @@ export class ChargingStation {
   public setSupervisionUrl(url: string): void {
     if (
       this.getSupervisionUrlOcppConfiguration() &&
-      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey())
+      isNotEmptyString(this.getSupervisionUrlOcppKey())
     ) {
-      ChargingStationConfigurationUtils.setConfigurationKeyValue(
-        this,
-        this.getSupervisionUrlOcppKey(),
-        url
-      );
+      setConfigurationKeyValue(this, this.getSupervisionUrlOcppKey(), url);
     } else {
       this.stationInfo.supervisionUrls = url;
       this.saveStationInfo();
@@ -485,24 +554,25 @@ export class ChargingStation {
           .catch((error) => {
             logger.error(
               `${this.logPrefix()} Error while sending '${RequestCommand.HEARTBEAT}':`,
-              error
+              error,
             );
           });
       }, this.getHeartbeatInterval());
       logger.info(
-        `${this.logPrefix()} Heartbeat started every ${Utils.formatDurationMilliSeconds(
-          this.getHeartbeatInterval()
-        )}`
+        `${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds(
+          this.getHeartbeatInterval(),
+        )}`,
       );
     } else if (this.heartbeatSetInterval) {
       logger.info(
-        `${this.logPrefix()} Heartbeat already started every ${Utils.formatDurationMilliSeconds(
-          this.getHeartbeatInterval()
-        )}`
+        `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds(
+          this.getHeartbeatInterval(),
+        )}`,
       );
     } 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`,
       );
     }
   }
@@ -524,38 +594,41 @@ 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()}`
+        `${this.logPrefix()} Trying to start MeterValues on non existing connector id
+          ${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 (
       this.getConnectorStatus(connectorId)?.transactionStarted === true &&
-      Utils.isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId)
+      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;
     }
     if (interval > 0) {
-      this.getConnectorStatus(connectorId).transactionSetInterval = setInterval(() => {
+      this.getConnectorStatus(connectorId)!.transactionSetInterval = setInterval(() => {
         // FIXME: Implement OCPP version agnostic helpers
         const meterValue: MeterValue = OCPP16ServiceUtils.buildMeterValue(
           this,
           connectorId,
-          this.getConnectorStatus(connectorId).transactionId,
-          interval
+          this.getConnectorStatus(connectorId)!.transactionId!,
+          interval,
         );
         this.ocppRequestService
           .requestHandler<MeterValuesRequest, MeterValuesResponse>(
@@ -565,12 +638,12 @@ export class ChargingStation {
               connectorId,
               transactionId: this.getConnectorStatus(connectorId)?.transactionId,
               meterValue: [meterValue],
-            }
+            },
           )
           .catch((error) => {
             logger.error(
               `${this.logPrefix()} Error while sending '${RequestCommand.METER_VALUES}':`,
-              error
+              error,
             );
           });
       }, interval);
@@ -578,7 +651,7 @@ export class ChargingStation {
       logger.error(
         `${this.logPrefix()} Charging station ${
           StandardParametersKey.MeterValueSampleInterval
-        } configuration set to ${interval}, not sending MeterValues`
+        } configuration set to ${interval}, not sending MeterValues`,
       );
     }
   }
@@ -596,29 +669,32 @@ export class ChargingStation {
         if (this.getEnableStatistics() === true) {
           this.performanceStatistics?.start();
         }
+        if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) {
+          this.startReservationExpirationSetInterval();
+        }
         this.openWSConnection();
         // Monitor charging station template file
-        this.templateFileWatcher = FileUtils.watchJsonFile(
+        this.templateFileWatcher = watchJsonFile(
           this.templateFile,
           FileType.ChargingStationTemplate,
           this.logPrefix(),
           undefined,
           (event, filename): void => {
-            if (Utils.isNotEmptyString(filename) && event === 'change') {
+            if (isNotEmptyString(filename) && event === 'change') {
               try {
                 logger.debug(
                   `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
                     this.templateFile
-                  } file have changed, reload`
+                  } file have changed, reload`,
                 );
-                this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
+                this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
                 // Initialize
                 this.initialize();
+                this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo)!);
                 // Restart the ATG
                 this.stopAutomaticTransactionGenerator();
-                if (
-                  this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true
-                ) {
+                delete this.automaticTransactionGeneratorConfiguration;
+                if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
                   this.startAutomaticTransactionGenerator();
                 }
                 if (this.getEnableStatistics() === true) {
@@ -630,14 +706,14 @@ export class ChargingStation {
               } catch (error) {
                 logger.error(
                   `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
-                  error
+                  error,
                 );
               }
             }
-          }
+          },
         );
         this.started = true;
-        parentPort?.postMessage(MessageChannelUtils.buildStartedMessage(this));
+        parentPort?.postMessage(buildStartedMessage(this));
         this.starting = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already starting...`);
@@ -656,12 +732,16 @@ export class ChargingStation {
         if (this.getEnableStatistics() === true) {
           this.performanceStatistics?.stop();
         }
+        if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) {
+          this.stopReservationExpirationSetInterval();
+        }
         this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
         this.templateFileWatcher?.close();
-        this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
+        this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
         delete this.bootNotificationResponse;
         this.started = false;
-        parentPort?.postMessage(MessageChannelUtils.buildStoppedMessage(this));
+        this.saveConfiguration();
+        parentPort?.postMessage(buildStoppedMessage(this));
         this.stopping = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already stopping...`);
@@ -673,24 +753,17 @@ export class ChargingStation {
 
   public async reset(reason?: StopTransactionReason): Promise<void> {
     await this.stop(reason);
-    await Utils.sleep(this.stationInfo.resetTime);
+    await sleep(this.stationInfo.resetTime!);
     this.initialize();
     this.start();
   }
 
   public saveOcppConfiguration(): void {
     if (this.getOcppPersistentConfiguration()) {
-      this.saveConfiguration({ stationInfo: false, connectors: false, evses: false });
+      this.saveConfiguration();
     }
   }
 
-  public hasFeatureProfile(featureProfile: SupportedFeatureProfiles): boolean | undefined {
-    return ChargingStationConfigurationUtils.getConfigurationKey(
-      this,
-      StandardParametersKey.SupportedFeatureProfiles
-    )?.value?.includes(featureProfile);
-  }
-
   public bufferMessage(message: string): void {
     this.messageBuffer.add(message);
   }
@@ -700,20 +773,20 @@ export class ChargingStation {
     params: { closeOpened?: boolean; terminateOpened?: boolean } = {
       closeOpened: false,
       terminateOpened: false,
-    }
+    },
   ): void {
-    options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
-    params.closeOpened = params?.closeOpened ?? false;
-    params.terminateOpened = params?.terminateOpened ?? false;
+    options = { handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()), ...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`
+        `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()}
+          on stopped charging station`,
       );
       return;
     }
     if (
-      !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) &&
-      !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)
+      !isNullOrUndefined(this.stationInfo.supervisionUser) &&
+      !isNullOrUndefined(this.stationInfo.supervisionPassword)
     ) {
       options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`;
     }
@@ -726,35 +799,36 @@ 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;
     }
 
     logger.info(
-      `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`
+      `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`,
     );
 
     this.wsConnection = new WebSocket(
       this.wsConnectionUrl,
       `ocpp${this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16}`,
-      options
+      options,
     );
 
     // Handle WebSocket message
     this.wsConnection.on(
       'message',
-      this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void
+      this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void,
     );
     // Handle WebSocket error
     this.wsConnection.on(
       'error',
-      this.onError.bind(this) as (this: WebSocket, error: Error) => void
+      this.onError.bind(this) as (this: WebSocket, error: Error) => void,
     );
     // Handle WebSocket close
     this.wsConnection.on(
       'close',
-      this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void
+      this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void,
     );
     // Handle WebSocket open
     this.wsConnection.on('open', this.onOpen.bind(this) as (this: WebSocket) => void);
@@ -771,39 +845,63 @@ export class ChargingStation {
     }
   }
 
-  public startAutomaticTransactionGenerator(
-    connectorIds?: number[],
-    automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration
-  ): void {
-    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(
-      automaticTransactionGeneratorConfiguration ??
-        this.getAutomaticTransactionGeneratorConfigurationFromTemplate(),
-      this
-    );
-    if (Utils.isNotEmptyArray(connectorIds)) {
-      for (const connectorId of connectorIds) {
+  public getAutomaticTransactionGeneratorConfiguration(): AutomaticTransactionGeneratorConfiguration {
+    if (isNullOrUndefined(this.automaticTransactionGeneratorConfiguration)) {
+      let automaticTransactionGeneratorConfiguration:
+        | AutomaticTransactionGeneratorConfiguration
+        | undefined;
+      const automaticTransactionGeneratorConfigurationFromFile =
+        this.getConfigurationFromFile()?.automaticTransactionGenerator;
+      if (
+        this.getAutomaticTransactionGeneratorPersistentConfiguration() &&
+        automaticTransactionGeneratorConfigurationFromFile
+      ) {
+        automaticTransactionGeneratorConfiguration =
+          automaticTransactionGeneratorConfigurationFromFile;
+      } else {
+        automaticTransactionGeneratorConfiguration =
+          this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+      }
+      this.automaticTransactionGeneratorConfiguration = {
+        ...Constants.DEFAULT_ATG_CONFIGURATION,
+        ...automaticTransactionGeneratorConfiguration,
+      };
+    }
+    return this.automaticTransactionGeneratorConfiguration!;
+  }
+
+  public getAutomaticTransactionGeneratorStatuses(): Status[] | undefined {
+    return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses;
+  }
+
+  public startAutomaticTransactionGenerator(connectorIds?: number[]): void {
+    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this);
+    if (isNotEmptyArray(connectorIds)) {
+      for (const connectorId of connectorIds!) {
         this.automaticTransactionGenerator?.startConnector(connectorId);
       }
     } else {
       this.automaticTransactionGenerator?.start();
     }
-    parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    this.saveAutomaticTransactionGeneratorConfiguration();
+    parentPort?.postMessage(buildUpdatedMessage(this));
   }
 
   public stopAutomaticTransactionGenerator(connectorIds?: number[]): void {
-    if (Utils.isNotEmptyArray(connectorIds)) {
-      for (const connectorId of connectorIds) {
+    if (isNotEmptyArray(connectorIds)) {
+      for (const connectorId of connectorIds!) {
         this.automaticTransactionGenerator?.stopConnector(connectorId);
       }
     } else {
       this.automaticTransactionGenerator?.stop();
     }
-    parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    this.saveAutomaticTransactionGeneratorConfiguration();
+    parentPort?.postMessage(buildUpdatedMessage(this));
   }
 
   public async stopTransactionOnConnector(
     connectorId: number,
-    reason = StopTransactionReason.NONE
+    reason = StopTransactionReason.NONE,
   ): Promise<StopTransactionResponse> {
     const transactionId = this.getConnectorStatus(connectorId)?.transactionId;
     if (
@@ -815,7 +913,7 @@ export class ChargingStation {
       const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
         this,
         connectorId,
-        this.getEnergyActiveImportRegisterByTransactionId(transactionId)
+        this.getEnergyActiveImportRegisterByTransactionId(transactionId!),
       );
       await this.ocppRequestService.requestHandler<MeterValuesRequest, MeterValuesResponse>(
         this,
@@ -824,7 +922,7 @@ export class ChargingStation {
           connectorId,
           transactionId,
           meterValue: [transactionEndMeterValue],
-        }
+        },
       );
     }
     return this.ocppRequestService.requestHandler<StopTransactionRequest, StopTransactionResponse>(
@@ -832,17 +930,180 @@ export class ChargingStation {
       RequestCommand.STOP_TRANSACTION,
       {
         transactionId,
-        meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true),
+        meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId!, true),
         reason,
-      }
+      },
+    );
+  }
+
+  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:
+        break;
+    }
+  }
+
+  public getReservationBy(
+    filterKey: ReservationFilterKey,
+    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 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 isConnectorReservable(
+    reservationId: number,
+    idTag?: string,
+    connectorId?: number,
+  ): boolean {
+    const reservationExists = !isUndefined(this.getReservationBy('reservationId', reservationId));
+    if (arguments.length === 1) {
+      return !reservationExists;
+    } else if (arguments.length > 1) {
+      const userReservationExists =
+        !isUndefined(idTag) && isUndefined(this.getReservationBy('idTag', idTag!)) ? false : true;
+      const notConnectorZero = isUndefined(connectorId) ? true : connectorId! > 0;
+      const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0;
+      return (
+        !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable
+      );
+    }
+    return false;
+  }
+
+  private getNumberOfReservableConnectors(): number {
+    let reservableConnectors = 0;
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        reservableConnectors += countReservableConnectors(evseStatus.connectors);
+      }
+    } else {
+      reservableConnectors = countReservableConnectors(this.connectors);
+    }
+    return reservableConnectors - 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 numberOfReservations;
+  }
+
   private flushMessageBuffer(): void {
     if (this.messageBuffer.size > 0) {
       for (const message of this.messageBuffer.values()) {
-        let beginId: string;
-        let commandName: RequestCommand;
+        let beginId: string | undefined;
+        let commandName: RequestCommand | undefined;
         const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse;
         const isRequest = messageType === MessageType.CALL_MESSAGE;
         if (isRequest) {
@@ -850,11 +1111,11 @@ export class ChargingStation {
           beginId = PerformanceStatistics.beginMeasure(commandName);
         }
         this.wsConnection?.send(message);
-        isRequest && PerformanceStatistics.endMeasure(commandName, beginId);
+        isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!);
         logger.debug(
           `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString(
-            messageType
-          )} payload sent: ${message}`
+            messageType,
+          )} payload sent: ${message}`,
         );
         this.messageBuffer.delete(message);
       }
@@ -865,68 +1126,58 @@ 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;
   }
 
   private getTemplateFromFile(): ChargingStationTemplate | undefined {
-    let template: ChargingStationTemplate;
+    let template: ChargingStationTemplate | undefined;
     try {
-      if (this.sharedLRUCache.hasChargingStationTemplate(this.stationInfo?.templateHash)) {
-        template = this.sharedLRUCache.getChargingStationTemplate(this.stationInfo.templateHash);
+      if (this.sharedLRUCache.hasChargingStationTemplate(this.templateFileHash)) {
+        template = this.sharedLRUCache.getChargingStationTemplate(this.templateFileHash);
       } else {
         const measureId = `${FileType.ChargingStationTemplate} read`;
         const beginId = PerformanceStatistics.beginMeasure(measureId);
-        template = JSON.parse(
-          fs.readFileSync(this.templateFile, 'utf8')
-        ) as ChargingStationTemplate;
+        template = JSON.parse(readFileSync(this.templateFile, 'utf8')) as ChargingStationTemplate;
         PerformanceStatistics.endMeasure(measureId, beginId);
-        template.templateHash = crypto
-          .createHash(Constants.DEFAULT_HASH_ALGORITHM)
+        template.templateHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
           .update(JSON.stringify(template))
           .digest('hex');
         this.sharedLRUCache.setChargingStationTemplate(template);
+        this.templateFileHash = template.templateHash;
       }
     } catch (error) {
-      FileUtils.handleFileException(
+      handleFileException(
         this.templateFile,
         FileType.ChargingStationTemplate,
         error as NodeJS.ErrnoException,
-        this.logPrefix()
+        this.logPrefix(),
       );
     }
     return template;
   }
 
   private getStationInfoFromTemplate(): ChargingStationInfo {
-    const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile();
-    if (Utils.isNullOrUndefined(stationTemplate)) {
-      const errorMsg = `Failed to read charging station template file ${this.templateFile}`;
-      logger.error(`${this.logPrefix()} ${errorMsg}`);
-      throw new BaseError(errorMsg);
-    }
-    if (Utils.isEmptyObject(stationTemplate)) {
-      const errorMsg = `Empty charging station information from template file ${this.templateFile}`;
-      logger.error(`${this.logPrefix()} ${errorMsg}`);
-      throw new BaseError(errorMsg);
-    }
-    ChargingStationUtils.warnTemplateKeysDeprecation(
-      this.templateFile,
-      stationTemplate,
-      this.logPrefix()
-    );
-    const stationInfo: ChargingStationInfo =
-      ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
-    stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate);
-    stationInfo.chargingStationId = ChargingStationUtils.getChargingStationId(
-      this.index,
-      stationTemplate
-    );
+    const stationTemplate: ChargingStationTemplate = this.getTemplateFromFile()!;
+    checkTemplate(stationTemplate, this.logPrefix(), this.templateFile);
+    warnTemplateKeysDeprecation(stationTemplate, this.logPrefix(), this.templateFile);
+    if (stationTemplate?.Connectors) {
+      checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile);
+    }
+    const stationInfo: ChargingStationInfo = stationTemplateToStationInfo(stationTemplate);
+    stationInfo.hashId = getHashId(this.index, stationTemplate);
+    stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate);
     stationInfo.ocppVersion = stationTemplate?.ocppVersion ?? OCPPVersion.VERSION_16;
-    ChargingStationUtils.createSerialNumber(stationTemplate, stationInfo);
-    if (Utils.isNotEmptyArray(stationTemplate?.power)) {
+    createSerialNumber(stationTemplate, stationInfo);
+    if (isNotEmptyArray(stationTemplate?.power)) {
       stationTemplate.power = stationTemplate.power as number[];
-      const powerArrayRandomIndex = Math.floor(Utils.secureRandom() * stationTemplate.power.length);
+      const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length);
       stationInfo.maximumPower =
         stationTemplate?.powerUnit === PowerUnits.KILO_WATT
           ? stationTemplate.power[powerArrayRandomIndex] * 1000
@@ -941,13 +1192,13 @@ export class ChargingStation {
     stationInfo.firmwareVersionPattern =
       stationTemplate?.firmwareVersionPattern ?? Constants.SEMVER_PATTERN;
     if (
-      Utils.isNotEmptyString(stationInfo.firmwareVersion) &&
-      new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion) === false
+      isNotEmptyString(stationInfo.firmwareVersion) &&
+      new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion!) === false
     ) {
       logger.warn(
         `${this.logPrefix()} Firmware version '${stationInfo.firmwareVersion}' in template file ${
           this.templateFile
-        } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
+        } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`,
       );
     }
     stationInfo.firmwareUpgrade = merge<FirmwareUpgrade>(
@@ -957,23 +1208,23 @@ export class ChargingStation {
         },
         reset: true,
       },
-      stationTemplate?.firmwareUpgrade ?? {}
+      stationTemplate?.firmwareUpgrade ?? {},
     );
-    stationInfo.resetTime = !Utils.isNullOrUndefined(stationTemplate?.resetTime)
-      ? stationTemplate.resetTime * 1000
+    stationInfo.resetTime = !isNullOrUndefined(stationTemplate?.resetTime)
+      ? secondsToMilliseconds(stationTemplate.resetTime!)
       : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
-    // Initialize evses or connectors if needed (FIXME: should be factored out)
-    this.initializeConnectorsOrEvses(stationInfo);
     stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
-    ChargingStationUtils.createStationInfoHash(stationInfo);
     return stationInfo;
   }
 
   private getStationInfoFromFile(): ChargingStationInfo | undefined {
     let stationInfo: ChargingStationInfo | undefined;
-    this.getStationInfoPersistentConfiguration() &&
-      (stationInfo = this.getConfigurationFromFile()?.stationInfo);
-    stationInfo && ChargingStationUtils.createStationInfoHash(stationInfo);
+    if (this.getStationInfoPersistentConfiguration()) {
+      stationInfo = this.getConfigurationFromFile()?.stationInfo;
+      if (stationInfo) {
+        delete stationInfo?.infoHash;
+      }
+    }
     return stationInfo;
   }
 
@@ -983,25 +1234,21 @@ export class ChargingStation {
     // Priority:
     // 1. charging station info from template
     // 2. charging station info from configuration file
-    // 3. charging station info attribute
     if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) {
-      if (this.stationInfo?.infoHash === stationInfoFromFile?.infoHash) {
-        return this.stationInfo;
-      }
-      return stationInfoFromFile;
+      return stationInfoFromFile!;
     }
     stationInfoFromFile &&
-      ChargingStationUtils.propagateSerialNumber(
-        this.getTemplateFromFile(),
+      propagateSerialNumber(
+        this.getTemplateFromFile()!,
         stationInfoFromFile,
-        stationInfoFromTemplate
+        stationInfoFromTemplate,
       );
     return stationInfoFromTemplate;
   }
 
   private saveStationInfo(): void {
     if (this.getStationInfoPersistentConfiguration()) {
-      this.saveConfiguration({ ocppConfiguration: false, connectors: false, evses: false });
+      this.saveConfiguration();
     }
   }
 
@@ -1013,51 +1260,63 @@ export class ChargingStation {
     return this.stationInfo?.stationInfoPersistentConfiguration ?? true;
   }
 
+  private getAutomaticTransactionGeneratorPersistentConfiguration(): boolean {
+    return this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration ?? true;
+  }
+
   private handleUnsupportedVersion(version: OCPPVersion) {
-    const errMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
-    logger.error(`${this.logPrefix()} ${errMsg}`);
-    throw new BaseError(errMsg);
+    const errorMsg = `Unsupported protocol version '${version}' configured
+      in template file ${this.templateFile}`;
+    logger.error(`${this.logPrefix()} ${errorMsg}`);
+    throw new BaseError(errorMsg);
   }
 
   private initialize(): void {
-    this.configurationFile = path.join(
-      path.dirname(this.templateFile.replace('station-templates', 'configurations')),
-      `${ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile())}.json`
+    const stationTemplate = this.getTemplateFromFile()!;
+    checkTemplate(stationTemplate, this.logPrefix(), this.templateFile);
+    this.configurationFile = join(
+      dirname(this.templateFile.replace('station-templates', 'configurations')),
+      `${getHashId(this.index, stationTemplate)}.json`,
     );
+    const chargingStationConfiguration = this.getConfigurationFromFile();
+    if (
+      chargingStationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+      // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+      (chargingStationConfiguration?.connectorsStatus || chargingStationConfiguration?.evsesStatus)
+    ) {
+      this.initializeConnectorsOrEvsesFromFile(chargingStationConfiguration);
+    } else {
+      this.initializeConnectorsOrEvsesFromTemplate(stationTemplate);
+    }
     this.stationInfo = this.getStationInfo();
     if (
       this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
-      Utils.isNotEmptyString(this.stationInfo.firmwareVersion) &&
-      Utils.isNotEmptyString(this.stationInfo.firmwareVersionPattern)
+      isNotEmptyString(this.stationInfo.firmwareVersion) &&
+      isNotEmptyString(this.stationInfo.firmwareVersionPattern)
     ) {
       const patternGroup: number | undefined =
         this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
         this.stationInfo.firmwareVersion?.split('.').length;
-      const match = this.stationInfo?.firmwareVersion
-        ?.match(new RegExp(this.stationInfo.firmwareVersionPattern))
-        ?.slice(1, patternGroup + 1);
+      const match = this.stationInfo
+        .firmwareVersion!.match(new RegExp(this.stationInfo.firmwareVersionPattern!))!
+        .slice(1, patternGroup! + 1);
       const patchLevelIndex = match.length - 1;
       match[patchLevelIndex] = (
-        Utils.convertToInt(match[patchLevelIndex]) +
-        this.stationInfo.firmwareUpgrade?.versionUpgrade?.step
+        convertToInt(match[patchLevelIndex]) +
+        this.stationInfo.firmwareUpgrade!.versionUpgrade!.step!
       ).toString();
       this.stationInfo.firmwareVersion = match?.join('.');
     }
     this.saveStationInfo();
-    // Avoid duplication of connectors or evses related information in RAM
-    delete this.stationInfo?.Connectors;
-    delete this.stationInfo?.Evses;
     this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
     if (this.getEnableStatistics() === true) {
       this.performanceStatistics = PerformanceStatistics.getInstance(
         this.stationInfo.hashId,
-        this.stationInfo.chargingStationId,
-        this.configuredSupervisionUrl
+        this.stationInfo.chargingStationId!,
+        this.configuredSupervisionUrl,
       );
     }
-    this.bootNotificationRequest = ChargingStationUtils.createBootNotificationRequest(
-      this.stationInfo
-    );
+    this.bootNotificationRequest = createBootNotificationRequest(this.stationInfo);
     this.powerDivider = this.getPowerDivider();
     // OCPP configuration
     this.ocppConfiguration = this.getOcppConfiguration();
@@ -1066,7 +1325,7 @@ export class ChargingStation {
     if (this.stationInfo?.autoRegister === true) {
       this.bootNotificationResponse = {
         currentTime: new Date(),
-        interval: this.getHeartbeatInterval() / 1000,
+        interval: millisecondsToSeconds(this.getHeartbeatInterval()),
         status: RegistrationStatusEnumType.ACCEPTED,
       };
     }
@@ -1079,7 +1338,7 @@ export class ChargingStation {
         this.ocppIncomingRequestService =
           OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>();
         this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
-          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
+          OCPP16ResponseService.getInstance<OCPP16ResponseService>(),
         );
         break;
       case OCPPVersion.VERSION_20:
@@ -1087,7 +1346,7 @@ export class ChargingStation {
         this.ocppIncomingRequestService =
           OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>();
         this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
-          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
+          OCPP20ResponseService.getInstance<OCPP20ResponseService>(),
         );
         break;
       default:
@@ -1097,193 +1356,143 @@ export class ChargingStation {
   }
 
   private initializeOcppConfiguration(): void {
-    if (
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.HeartbeatInterval
-      )
-    ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
-        this,
-        StandardParametersKey.HeartbeatInterval,
-        '0'
-      );
+    if (!getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)) {
+      addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0');
     }
-    if (
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.HeartBeatInterval
-      )
-    ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
-        this,
-        StandardParametersKey.HeartBeatInterval,
-        '0',
-        { visible: false }
-      );
+    if (!getConfigurationKey(this, StandardParametersKey.HeartBeatInterval)) {
+      addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', { visible: false });
     }
     if (
       this.getSupervisionUrlOcppConfiguration() &&
-      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
-      !ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
+      isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
+      !getConfigurationKey(this, this.getSupervisionUrlOcppKey())
     ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
+      addConfigurationKey(
         this,
         this.getSupervisionUrlOcppKey(),
         this.configuredSupervisionUrl.href,
-        { reboot: true }
+        { reboot: true },
       );
     } else if (
       !this.getSupervisionUrlOcppConfiguration() &&
-      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
-      ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
+      isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
+      getConfigurationKey(this, this.getSupervisionUrlOcppKey())
     ) {
-      ChargingStationConfigurationUtils.deleteConfigurationKey(
-        this,
-        this.getSupervisionUrlOcppKey(),
-        { save: false }
-      );
+      deleteConfigurationKey(this, this.getSupervisionUrlOcppKey(), { save: false });
     }
     if (
-      Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        this.stationInfo.amperageLimitationOcppKey
-      )
+      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
+      !getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)
     ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
+      addConfigurationKey(
         this,
-        this.stationInfo.amperageLimitationOcppKey,
+        this.stationInfo.amperageLimitationOcppKey!,
         (
-          this.stationInfo.maximumAmperage *
-          ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
-        ).toString()
+          this.stationInfo.maximumAmperage! * getAmperageLimitationUnitDivider(this.stationInfo)
+        ).toString(),
       );
     }
-    if (
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.SupportedFeatureProfiles
-      )
-    ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
+    if (!getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)) {
+      addConfigurationKey(
         this,
         StandardParametersKey.SupportedFeatureProfiles,
-        `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`
+        `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`,
       );
     }
-    ChargingStationConfigurationUtils.addConfigurationKey(
+    addConfigurationKey(
       this,
       StandardParametersKey.NumberOfConnectors,
       this.getNumberOfConnectors().toString(),
       { readonly: true },
-      { overwrite: true }
+      { overwrite: true },
     );
-    if (
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.MeterValuesSampledData
-      )
-    ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
+    if (!getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData)) {
+      addConfigurationKey(
         this,
         StandardParametersKey.MeterValuesSampledData,
-        MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
       );
     }
-    if (
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.ConnectorPhaseRotation
-      )
-    ) {
-      const connectorPhaseRotation = [];
+    if (!getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation)) {
+      const connectorsPhaseRotation: string[] = [];
       if (this.hasEvses) {
         for (const evseStatus of this.evses.values()) {
           for (const connectorId of evseStatus.connectors.keys()) {
-            // AC/DC
-            if (connectorId === 0 && this.getNumberOfPhases() === 0) {
-              connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
-            } else if (connectorId > 0 && this.getNumberOfPhases() === 0) {
-              connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
-              // AC
-            } else if (connectorId > 0 && this.getNumberOfPhases() === 1) {
-              connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
-            } else if (connectorId > 0 && this.getNumberOfPhases() === 3) {
-              connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
-            }
+            connectorsPhaseRotation.push(
+              getPhaseRotationValue(connectorId, this.getNumberOfPhases())!,
+            );
           }
         }
       } else {
         for (const connectorId of this.connectors.keys()) {
-          // AC/DC
-          if (connectorId === 0 && this.getNumberOfPhases() === 0) {
-            connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
-          } else if (connectorId > 0 && this.getNumberOfPhases() === 0) {
-            connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
-            // AC
-          } else if (connectorId > 0 && this.getNumberOfPhases() === 1) {
-            connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
-          } else if (connectorId > 0 && this.getNumberOfPhases() === 3) {
-            connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
-          }
+          connectorsPhaseRotation.push(
+            getPhaseRotationValue(connectorId, this.getNumberOfPhases())!,
+          );
         }
       }
-      ChargingStationConfigurationUtils.addConfigurationKey(
+      addConfigurationKey(
         this,
         StandardParametersKey.ConnectorPhaseRotation,
-        connectorPhaseRotation.toString()
+        connectorsPhaseRotation.toString(),
       );
     }
-    if (
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.AuthorizeRemoteTxRequests
-      )
-    ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
-        this,
-        StandardParametersKey.AuthorizeRemoteTxRequests,
-        'true'
-      );
-    }
-    if (
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.LocalAuthListEnabled
-      ) &&
-      ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.SupportedFeatureProfiles
-      )?.value?.includes(SupportedFeatureProfiles.LocalAuthListManagement)
-    ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
-        this,
-        StandardParametersKey.LocalAuthListEnabled,
-        'false'
-      );
+    if (!getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests)) {
+      addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true');
     }
     if (
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.ConnectionTimeOut
+      !getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) &&
+      getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)?.value?.includes(
+        SupportedFeatureProfiles.LocalAuthListManagement,
       )
     ) {
-      ChargingStationConfigurationUtils.addConfigurationKey(
+      addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false');
+    }
+    if (!getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)) {
+      addConfigurationKey(
         this,
         StandardParametersKey.ConnectionTimeOut,
-        Constants.DEFAULT_CONNECTION_TIMEOUT.toString()
+        Constants.DEFAULT_CONNECTION_TIMEOUT.toString(),
       );
     }
     this.saveOcppConfiguration();
   }
 
-  private initializeConnectorsOrEvses(stationInfo: ChargingStationInfo) {
-    if (stationInfo?.Connectors && !stationInfo?.Evses) {
-      this.initializeConnectors(stationInfo);
-    } else if (stationInfo?.Evses && !stationInfo?.Connectors) {
-      this.initializeEvses(stationInfo);
-    } else if (stationInfo?.Evses && stationInfo?.Connectors) {
+  private initializeConnectorsOrEvsesFromFile(configuration: ChargingStationConfiguration): void {
+    if (configuration?.connectorsStatus && !configuration?.evsesStatus) {
+      for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) {
+        this.connectors.set(connectorId, cloneObject<ConnectorStatus>(connectorStatus));
+      }
+    } else if (configuration?.evsesStatus && !configuration?.connectorsStatus) {
+      for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) {
+        const evseStatus = cloneObject<EvseStatusConfiguration>(evseStatusConfiguration);
+        delete evseStatus.connectorsStatus;
+        this.evses.set(evseId, {
+          ...(evseStatus as EvseStatus),
+          connectors: new Map<number, ConnectorStatus>(
+            evseStatusConfiguration.connectorsStatus!.map((connectorStatus, connectorId) => [
+              connectorId,
+              connectorStatus,
+            ]),
+          ),
+        });
+      }
+    } else if (configuration?.evsesStatus && configuration?.connectorsStatus) {
+      const errorMsg = `Connectors and evses defined at the same time in configuration file ${this.configurationFile}`;
+      logger.error(`${this.logPrefix()} ${errorMsg}`);
+      throw new BaseError(errorMsg);
+    } else {
+      const errorMsg = `No connectors or evses defined in configuration file ${this.configurationFile}`;
+      logger.error(`${this.logPrefix()} ${errorMsg}`);
+      throw new BaseError(errorMsg);
+    }
+  }
+
+  private initializeConnectorsOrEvsesFromTemplate(stationTemplate: ChargingStationTemplate) {
+    if (stationTemplate?.Connectors && !stationTemplate?.Evses) {
+      this.initializeConnectorsFromTemplate(stationTemplate);
+    } else if (stationTemplate?.Evses && !stationTemplate?.Connectors) {
+      this.initializeEvsesFromTemplate(stationTemplate);
+    } else if (stationTemplate?.Evses && stationTemplate?.Connectors) {
       const errorMsg = `Connectors and evses defined at the same time in template file ${this.templateFile}`;
       logger.error(`${this.logPrefix()} ${errorMsg}`);
       throw new BaseError(errorMsg);
@@ -1294,87 +1503,61 @@ export class ChargingStation {
     }
   }
 
-  private initializeConnectors(stationInfo: ChargingStationInfo): void {
-    if (!stationInfo?.Connectors && this.connectors.size === 0) {
-      const logMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`;
-      logger.error(`${this.logPrefix()} ${logMsg}`);
-      throw new BaseError(logMsg);
+  private initializeConnectorsFromTemplate(stationTemplate: ChargingStationTemplate): void {
+    if (!stationTemplate?.Connectors && this.connectors.size === 0) {
+      const errorMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`;
+      logger.error(`${this.logPrefix()} ${errorMsg}`);
+      throw new BaseError(errorMsg);
     }
-    if (!stationInfo?.Connectors[0]) {
+    if (!stationTemplate?.Connectors?.[0]) {
       logger.warn(
         `${this.logPrefix()} Charging station information from template ${
           this.templateFile
-        } with no connector id 0 configuration`
+        } with no connector id 0 configuration`,
       );
     }
-    if (stationInfo?.Connectors) {
-      const configuredMaxConnectors =
-        ChargingStationUtils.getConfiguredNumberOfConnectors(stationInfo);
-      ChargingStationUtils.checkConfiguredMaxConnectors(
-        configuredMaxConnectors,
-        this.templateFile,
-        this.logPrefix()
-      );
-      const connectorsConfigHash = crypto
-        .createHash(Constants.DEFAULT_HASH_ALGORITHM)
-        .update(`${JSON.stringify(stationInfo?.Connectors)}${configuredMaxConnectors.toString()}`)
+    if (stationTemplate?.Connectors) {
+      const { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors } =
+        checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile);
+      const connectorsConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
+        .update(
+          `${JSON.stringify(stationTemplate?.Connectors)}${configuredMaxConnectors.toString()}`,
+        )
         .digest('hex');
       const connectorsConfigChanged =
         this.connectors?.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash;
       if (this.connectors?.size === 0 || connectorsConfigChanged) {
         connectorsConfigChanged && this.connectors.clear();
         this.connectorsConfigurationHash = connectorsConfigHash;
-        const templateMaxConnectors = ChargingStationUtils.getMaxNumberOfConnectors(
-          stationInfo.Connectors
-        );
-        ChargingStationUtils.checkTemplateMaxConnectors(
-          templateMaxConnectors,
-          this.templateFile,
-          this.logPrefix()
-        );
-        const templateMaxAvailableConnectors = stationInfo?.Connectors[0]
-          ? templateMaxConnectors - 1
-          : templateMaxConnectors;
-        if (
-          configuredMaxConnectors > templateMaxAvailableConnectors &&
-          !stationInfo?.randomConnectors
-        ) {
-          logger.warn(
-            `${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${
-              this.templateFile
-            }, forcing random connector configurations affectation`
-          );
-          stationInfo.randomConnectors = true;
-        }
         if (templateMaxConnectors > 0) {
           for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
             if (
               connectorId === 0 &&
-              (!stationInfo?.Connectors[connectorId] ||
-                this.getUseConnectorId0(stationInfo) === false)
+              (!stationTemplate?.Connectors[connectorId] ||
+                this.getUseConnectorId0(stationTemplate) === false)
             ) {
               continue;
             }
             const templateConnectorId =
-              connectorId > 0 && stationInfo?.randomConnectors
-                ? Utils.getRandomInteger(templateMaxAvailableConnectors, 1)
+              connectorId > 0 && stationTemplate?.randomConnectors
+                ? getRandomInteger(templateMaxAvailableConnectors, 1)
                 : connectorId;
-            const connectorStatus = stationInfo?.Connectors[templateConnectorId];
-            ChargingStationUtils.checkStationInfoConnectorStatus(
+            const connectorStatus = stationTemplate?.Connectors[templateConnectorId];
+            checkStationInfoConnectorStatus(
               templateConnectorId,
               connectorStatus,
               this.logPrefix(),
-              this.templateFile
+              this.templateFile,
             );
-            this.connectors.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
+            this.connectors.set(connectorId, cloneObject<ConnectorStatus>(connectorStatus));
           }
-          ChargingStationUtils.initializeConnectorsMapStatus(this.connectors, this.logPrefix());
+          initializeConnectorsMapStatus(this.connectors, this.logPrefix());
           this.saveConnectorsStatus();
         } else {
           logger.warn(
             `${this.logPrefix()} Charging station information from template ${
               this.templateFile
-            } with no connectors configuration defined, cannot create connectors`
+            } with no connectors configuration defined, cannot create connectors`,
           );
         }
       }
@@ -1382,64 +1565,60 @@ export class ChargingStation {
       logger.warn(
         `${this.logPrefix()} Charging station information from template ${
           this.templateFile
-        } with no connectors configuration defined, using already defined connectors`
+        } with no connectors configuration defined, using already defined connectors`,
       );
     }
   }
 
-  private initializeEvses(stationInfo: ChargingStationInfo): void {
-    if (!stationInfo?.Evses && this.evses.size === 0) {
-      const logMsg = `No already defined evses and charging station information from template ${this.templateFile} with no evses configuration defined`;
-      logger.error(`${this.logPrefix()} ${logMsg}`);
-      throw new BaseError(logMsg);
+  private initializeEvsesFromTemplate(stationTemplate: ChargingStationTemplate): void {
+    if (!stationTemplate?.Evses && this.evses.size === 0) {
+      const errorMsg = `No already defined evses and charging station information from template ${this.templateFile} with no evses configuration defined`;
+      logger.error(`${this.logPrefix()} ${errorMsg}`);
+      throw new BaseError(errorMsg);
     }
-    if (!stationInfo?.Evses[0]) {
+    if (!stationTemplate?.Evses?.[0]) {
       logger.warn(
         `${this.logPrefix()} Charging station information from template ${
           this.templateFile
-        } with no evse id 0 configuration`
+        } with no evse id 0 configuration`,
       );
     }
-    if (!stationInfo?.Evses[0]?.Connectors[0]) {
+    if (!stationTemplate?.Evses?.[0]?.Connectors?.[0]) {
       logger.warn(
         `${this.logPrefix()} Charging station information from template ${
           this.templateFile
-        } with evse id 0 with no connector id 0 configuration`
+        } with evse id 0 with no connector id 0 configuration`,
       );
     }
-    if (stationInfo?.Evses) {
-      const evsesConfigHash = crypto
-        .createHash(Constants.DEFAULT_HASH_ALGORITHM)
-        .update(`${JSON.stringify(stationInfo?.Evses)}`)
+    if (stationTemplate?.Evses) {
+      const evsesConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
+        .update(JSON.stringify(stationTemplate?.Evses))
         .digest('hex');
       const evsesConfigChanged =
         this.evses?.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash;
       if (this.evses?.size === 0 || evsesConfigChanged) {
         evsesConfigChanged && this.evses.clear();
         this.evsesConfigurationHash = evsesConfigHash;
-        const templateMaxEvses = ChargingStationUtils.getMaxNumberOfEvses(stationInfo?.Evses);
+        const templateMaxEvses = getMaxNumberOfEvses(stationTemplate?.Evses);
         if (templateMaxEvses > 0) {
-          for (const evse in stationInfo.Evses) {
-            const evseId = Utils.convertToInt(evse);
+          for (const evse in stationTemplate.Evses) {
+            const evseId = convertToInt(evse);
             this.evses.set(evseId, {
-              connectors: ChargingStationUtils.buildConnectorsMap(
-                stationInfo?.Evses[evse]?.Connectors,
+              connectors: buildConnectorsMap(
+                stationTemplate?.Evses[evse]?.Connectors,
                 this.logPrefix(),
-                this.templateFile
+                this.templateFile,
               ),
               availability: AvailabilityType.Operative,
             });
-            ChargingStationUtils.initializeConnectorsMapStatus(
-              this.evses.get(evseId)?.connectors,
-              this.logPrefix()
-            );
+            initializeConnectorsMapStatus(this.evses.get(evseId)!.connectors, this.logPrefix());
           }
           this.saveEvsesStatus();
         } else {
           logger.warn(
             `${this.logPrefix()} Charging station information from template ${
               this.templateFile
-            } with no evses configuration defined, cannot create evses`
+            } with no evses configuration defined, cannot create evses`,
           );
         }
       }
@@ -1447,131 +1626,147 @@ export class ChargingStation {
       logger.warn(
         `${this.logPrefix()} Charging station information from template ${
           this.templateFile
-        } with no evses configuration defined, using already defined evses`
+        } with no evses configuration defined, using already defined evses`,
       );
     }
   }
 
   private getConfigurationFromFile(): ChargingStationConfiguration | undefined {
     let configuration: ChargingStationConfiguration | undefined;
-    if (this.configurationFile && fs.existsSync(this.configurationFile)) {
+    if (isNotEmptyString(this.configurationFile) && existsSync(this.configurationFile)) {
       try {
         if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
           configuration = this.sharedLRUCache.getChargingStationConfiguration(
-            this.configurationFileHash
+            this.configurationFileHash,
           );
         } else {
           const measureId = `${FileType.ChargingStationConfiguration} read`;
           const beginId = PerformanceStatistics.beginMeasure(measureId);
           configuration = JSON.parse(
-            fs.readFileSync(this.configurationFile, 'utf8')
+            readFileSync(this.configurationFile, 'utf8'),
           ) as ChargingStationConfiguration;
           PerformanceStatistics.endMeasure(measureId, beginId);
-          this.configurationFileHash = configuration.configurationHash;
           this.sharedLRUCache.setChargingStationConfiguration(configuration);
+          this.configurationFileHash = configuration.configurationHash!;
         }
       } catch (error) {
-        FileUtils.handleFileException(
+        handleFileException(
           this.configurationFile,
           FileType.ChargingStationConfiguration,
           error as NodeJS.ErrnoException,
-          this.logPrefix()
+          this.logPrefix(),
         );
       }
     }
     return configuration;
   }
 
-  private saveConnectorsStatus() {
-    if (this.getOcppPersistentConfiguration()) {
-      this.saveConfiguration({ stationInfo: false, ocppConfiguration: false, evses: false });
+  private saveAutomaticTransactionGeneratorConfiguration(): void {
+    if (this.getAutomaticTransactionGeneratorPersistentConfiguration()) {
+      this.saveConfiguration();
     }
   }
 
+  private saveConnectorsStatus() {
+    this.saveConfiguration();
+  }
+
   private saveEvsesStatus() {
-    if (this.getOcppPersistentConfiguration()) {
-      this.saveConfiguration({ stationInfo: false, ocppConfiguration: false, connectors: false });
-    }
+    this.saveConfiguration();
   }
 
-  private saveConfiguration(
-    params: {
-      stationInfo?: boolean;
-      ocppConfiguration?: boolean;
-      connectors?: boolean;
-      evses?: boolean;
-    } = { stationInfo: true, ocppConfiguration: true, connectors: true, evses: true }
-  ): void {
-    if (this.configurationFile) {
-      params = {
-        ...params,
-        ...{ stationInfo: true, ocppConfiguration: true, connectors: true, evses: true },
-      };
+  private saveConfiguration(): void {
+    if (isNotEmptyString(this.configurationFile)) {
       try {
-        if (!fs.existsSync(path.dirname(this.configurationFile))) {
-          fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
+        if (!existsSync(dirname(this.configurationFile))) {
+          mkdirSync(dirname(this.configurationFile), { recursive: true });
         }
-        const configurationData: ChargingStationConfiguration =
-          Utils.cloneObject(this.getConfigurationFromFile()) ?? {};
-        if (params.stationInfo && this.stationInfo) {
+        let configurationData: ChargingStationConfiguration = this.getConfigurationFromFile()
+          ? cloneObject<ChargingStationConfiguration>(this.getConfigurationFromFile()!)
+          : {};
+        if (this.getStationInfoPersistentConfiguration() && this.stationInfo) {
           configurationData.stationInfo = this.stationInfo;
+        } else {
+          delete configurationData.stationInfo;
         }
-        if (params.ocppConfiguration && this.ocppConfiguration?.configurationKey) {
+        if (this.getOcppPersistentConfiguration() && this.ocppConfiguration?.configurationKey) {
           configurationData.configurationKey = this.ocppConfiguration.configurationKey;
+        } else {
+          delete configurationData.configurationKey;
         }
-        if (params.connectors && this.connectors.size > 0) {
-          configurationData.connectorsStatus = [...this.connectors.values()].map(
-            // eslint-disable-next-line @typescript-eslint/no-unused-vars
-            ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest
-          );
+        configurationData = merge<ChargingStationConfiguration>(
+          configurationData,
+          buildChargingStationAutomaticTransactionGeneratorConfiguration(this),
+        );
+        if (
+          !this.getAutomaticTransactionGeneratorPersistentConfiguration() ||
+          !this.getAutomaticTransactionGeneratorConfiguration()
+        ) {
+          delete configurationData.automaticTransactionGenerator;
         }
-        if (params.evses && this.evses.size > 0) {
-          configurationData.evsesStatus = [...this.evses.values()].map((evseStatus) => {
-            const status = {
-              ...evseStatus,
-              connectorsStatus: [...evseStatus.connectors.values()].map(
-                // eslint-disable-next-line @typescript-eslint/no-unused-vars
-                ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest
-              ),
-            };
-            delete status.connectors;
-            return status as EvseStatusConfiguration;
-          });
+        if (this.connectors.size > 0) {
+          configurationData.connectorsStatus = buildConnectorsStatus(this);
+        } else {
+          delete configurationData.connectorsStatus;
+        }
+        if (this.evses.size > 0) {
+          configurationData.evsesStatus = buildEvsesStatus(this);
+        } else {
+          delete configurationData.evsesStatus;
         }
         delete configurationData.configurationHash;
-        const configurationHash = crypto
-          .createHash(Constants.DEFAULT_HASH_ALGORITHM)
-          .update(JSON.stringify(configurationData))
+        const configurationHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
+          .update(
+            JSON.stringify({
+              stationInfo: configurationData.stationInfo,
+              configurationKey: configurationData.configurationKey,
+              automaticTransactionGenerator: configurationData.automaticTransactionGenerator,
+            } as ChargingStationConfiguration),
+          )
           .digest('hex');
         if (this.configurationFileHash !== configurationHash) {
-          configurationData.configurationHash = configurationHash;
-          const measureId = `${FileType.ChargingStationConfiguration} write`;
-          const beginId = PerformanceStatistics.beginMeasure(measureId);
-          const fileDescriptor = fs.openSync(this.configurationFile, 'w');
-          fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
-          fs.closeSync(fileDescriptor);
-          PerformanceStatistics.endMeasure(measureId, beginId);
-          this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
-          this.configurationFileHash = configurationHash;
-          this.sharedLRUCache.setChargingStationConfiguration(configurationData);
+          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);
+            });
         } else {
           logger.debug(
             `${this.logPrefix()} Not saving unchanged charging station configuration file ${
               this.configurationFile
-            }`
+            }`,
           );
         }
       } catch (error) {
-        FileUtils.handleFileException(
+        handleFileException(
           this.configurationFile,
           FileType.ChargingStationConfiguration,
           error as NodeJS.ErrnoException,
-          this.logPrefix()
+          this.logPrefix(),
         );
       }
     } else {
       logger.error(
-        `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`
+        `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`,
       );
     }
   }
@@ -1581,16 +1776,11 @@ export class ChargingStation {
   }
 
   private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined {
-    let configuration: ChargingStationConfiguration | undefined;
-    if (this.getOcppPersistentConfiguration() === true) {
-      const configurationFromFile = this.getConfigurationFromFile();
-      configuration = configurationFromFile?.configurationKey && configurationFromFile;
-    }
-    if (!Utils.isNullOrUndefined(configuration)) {
-      delete configuration.stationInfo;
-      delete configuration.configurationHash;
+    const configurationKey = this.getConfigurationFromFile()?.configurationKey;
+    if (this.getOcppPersistentConfiguration() === true && configurationKey) {
+      return { configurationKey };
     }
-    return configuration;
+    return undefined;
   }
 
   private getOcppConfiguration(): ChargingStationOcppConfiguration | undefined {
@@ -1605,7 +1795,7 @@ export class ChargingStation {
   private async onOpen(): Promise<void> {
     if (this.isWebSocketConnectionOpened() === true) {
       logger.info(
-        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`
+        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`,
       );
       if (this.isRegistered() === false) {
         // Send BootNotification
@@ -1618,34 +1808,34 @@ export class ChargingStation {
             skipBufferingOnError: true,
           });
           if (this.isRegistered() === false) {
-            this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
-            await Utils.sleep(
+            this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount;
+            await sleep(
               this?.bootNotificationResponse?.interval
-                ? this.bootNotificationResponse.interval * 1000
-                : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL
+                ? secondsToMilliseconds(this.bootNotificationResponse.interval)
+                : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL,
             );
           }
         } while (
           this.isRegistered() === false &&
-          (registrationRetryCount <= this.getRegistrationMaxRetries() ||
+          (registrationRetryCount <= this.getRegistrationMaxRetries()! ||
             this.getRegistrationMaxRetries() === -1)
         );
       }
       if (this.isRegistered() === true) {
-        if (this.isInAcceptedState() === true) {
+        if (this.inAcceptedState() === true) {
           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 (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`,
         );
       }
       this.wsConnectionRestarted = false;
       this.autoReconnectRetryCount = 0;
-      parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+      parentPort?.postMessage(buildUpdatedMessage(this));
     } else {
       logger.warn(
-        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`
+        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`,
       );
     }
   }
@@ -1656,23 +1846,23 @@ export class ChargingStation {
       case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
       case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
         logger.info(
-          `${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(
-            code
-          )}' and reason '${reason.toString()}'`
+          `${this.logPrefix()} WebSocket normally closed with status '${getWebSocketCloseEventStatusString(
+            code,
+          )}' and reason '${reason.toString()}'`,
         );
         this.autoReconnectRetryCount = 0;
         break;
       // Abnormal close
       default:
         logger.error(
-          `${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(
-            code
-          )}' and reason '${reason.toString()}'`
+          `${this.logPrefix()} WebSocket abnormally closed with status '${getWebSocketCloseEventStatusString(
+            code,
+          )}' and reason '${reason.toString()}'`,
         );
         this.started === true && (await this.reconnect());
         break;
     }
-    parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    parentPort?.postMessage(buildUpdatedMessage(this));
   }
 
   private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined {
@@ -1683,10 +1873,10 @@ export class ChargingStation {
     throw new OCPPError(
       ErrorType.PROTOCOL_ERROR,
       `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString(
-        messageType
+        messageType,
       )} is not an array`,
       undefined,
-      cachedRequest as JsonType
+      cachedRequest as JsonType,
     );
   }
 
@@ -1697,15 +1887,15 @@ export class ChargingStation {
     }
     logger.debug(
       `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
-        request
-      )}`
+        request,
+      )}`,
     );
     // Process the message
     await this.ocppIncomingRequestService.incomingRequestHandler(
       this,
       messageId,
       commandName,
-      commandPayload
+      commandPayload,
     );
   }
 
@@ -1717,18 +1907,18 @@ export class ChargingStation {
         ErrorType.INTERNAL_ERROR,
         `Response for unknown message id ${messageId}`,
         undefined,
-        commandPayload
+        commandPayload,
       );
     }
     // Respond
     const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest(
       messageType,
-      messageId
-    );
+      messageId,
+    )!;
     logger.debug(
       `${this.logPrefix()} << Command '${
         requestCommandName ?? Constants.UNKNOWN_COMMAND
-      }' received response payload: ${JSON.stringify(response)}`
+      }' received response payload: ${JSON.stringify(response)}`,
     );
     responseCallback(commandPayload, requestPayload);
   }
@@ -1741,23 +1931,24 @@ export class ChargingStation {
         ErrorType.INTERNAL_ERROR,
         `Error response for unknown message id ${messageId}`,
         undefined,
-        { errorType, errorMessage, errorDetails }
+        { errorType, errorMessage, errorDetails },
       );
     }
-    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId);
+    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!;
     logger.debug(
       `${this.logPrefix()} << Command '${
         requestCommandName ?? Constants.UNKNOWN_COMMAND
-      }' received error response payload: ${JSON.stringify(errorResponse)}`
+      }' received error response payload: ${JSON.stringify(errorResponse)}`,
     );
     errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails));
   }
 
   private async onMessage(data: RawData): Promise<void> {
-    let request: IncomingRequest | Response | ErrorResponse;
-    let messageType: number;
-    let errMsg: string;
+    let request: IncomingRequest | Response | ErrorResponse | undefined;
+    let messageType: number | undefined;
+    let errorMsg: string;
     try {
+      // eslint-disable-next-line @typescript-eslint/no-base-to-string
       request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
       if (Array.isArray(request) === true) {
         [messageType] = request;
@@ -1778,21 +1969,26 @@ export class ChargingStation {
           // Unknown Message
           default:
             // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            errMsg = `Wrong message type ${messageType}`;
-            logger.error(`${this.logPrefix()} ${errMsg}`);
-            throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
+            errorMsg = `Wrong message type ${messageType}`;
+            logger.error(`${this.logPrefix()} ${errorMsg}`);
+            throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg);
         }
-        parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+        parentPort?.postMessage(buildUpdatedMessage(this));
       } else {
-        throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming message is not an array', null, {
-          request,
-        });
+        throw new OCPPError(
+          ErrorType.PROTOCOL_ERROR,
+          'Incoming message is not an array',
+          undefined,
+          {
+            request,
+          },
+        );
       }
     } catch (error) {
-      let commandName: IncomingRequestCommand;
-      let requestCommandName: RequestCommand | IncomingRequestCommand;
+      let commandName: IncomingRequestCommand | undefined;
+      let requestCommandName: RequestCommand | IncomingRequestCommand | undefined;
       let errorCallback: ErrorCallback;
-      const [, messageId] = request;
+      const [, messageId] = request!;
       switch (messageType) {
         case MessageType.CALL_MESSAGE:
           [, , commandName] = request as IncomingRequest;
@@ -1802,7 +1998,7 @@ export class ChargingStation {
         case MessageType.CALL_RESULT_MESSAGE:
         case MessageType.CALL_ERROR_MESSAGE:
           if (this.requests.has(messageId) === true) {
-            [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId);
+            [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!;
             // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
             errorCallback(error as OCPPError, false);
           } else {
@@ -1815,19 +2011,21 @@ export class ChargingStation {
         logger.warn(
           `${this.logPrefix()} Error thrown at incoming OCPP command '${
             commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+            // eslint-disable-next-line @typescript-eslint/no-base-to-string
           }' message '${data.toString()}' handling is not an OCPPError:`,
-          error
+          error,
         );
       }
       logger.error(
         `${this.logPrefix()} Incoming OCPP command '${
           commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+          // eslint-disable-next-line @typescript-eslint/no-base-to-string
         }' message '${data.toString()}'${
           messageType !== MessageType.CALL_MESSAGE
             ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
             : ''
         } processing error:`,
-        error
+        error,
       );
     }
   }
@@ -1849,45 +2047,27 @@ export class ChargingStation {
     if (this.getMeteringPerTransaction() === true) {
       return (
         (rounded === true
-          ? Math.round(connectorStatus?.transactionEnergyActiveImportRegisterValue)
+          ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue!)
           : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0
       );
     }
     return (
       (rounded === true
-        ? Math.round(connectorStatus?.energyActiveImportRegisterValue)
+        ? Math.round(connectorStatus.energyActiveImportRegisterValue!)
         : connectorStatus?.energyActiveImportRegisterValue) ?? 0
     );
   }
 
-  private getUseConnectorId0(stationInfo?: ChargingStationInfo): boolean {
-    const localStationInfo = stationInfo ?? this.stationInfo;
-    return localStationInfo?.useConnectorId0 ?? true;
-  }
-
-  private getNumberOfRunningTransactions(): number {
-    let trxCount = 0;
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.transactionStarted === true) {
-            trxCount++;
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-          trxCount++;
-        }
-      }
-    }
-    return trxCount;
+  private getUseConnectorId0(stationTemplate?: ChargingStationTemplate): boolean {
+    return stationTemplate?.useConnectorId0 ?? true;
   }
 
   private async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise<void> {
     if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId === 0) {
+          continue;
+        }
         for (const [connectorId, connectorStatus] of evseStatus.connectors) {
           if (connectorStatus.transactionStarted === true) {
             await this.stopTransactionOnConnector(connectorId, reason);
@@ -1905,19 +2085,10 @@ export class ChargingStation {
 
   // 0 for disabling
   private getConnectionTimeout(): number {
-    if (
-      ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        StandardParametersKey.ConnectionTimeOut
-      )
-    ) {
+    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)) {
       return (
-        parseInt(
-          ChargingStationConfigurationUtils.getConfigurationKey(
-            this,
-            StandardParametersKey.ConnectionTimeOut
-          ).value
-        ) ?? Constants.DEFAULT_CONNECTION_TIMEOUT
+        parseInt(getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)!.value!) ??
+        Constants.DEFAULT_CONNECTION_TIMEOUT
       );
     }
     return Constants.DEFAULT_CONNECTION_TIMEOUT;
@@ -1925,25 +2096,18 @@ export class ChargingStation {
 
   // -1 for unlimited, 0 for disabling
   private getAutoReconnectMaxRetries(): number | undefined {
-    if (!Utils.isUndefined(this.stationInfo.autoReconnectMaxRetries)) {
-      return this.stationInfo.autoReconnectMaxRetries;
-    }
-    if (!Utils.isUndefined(Configuration.getAutoReconnectMaxRetries())) {
-      return Configuration.getAutoReconnectMaxRetries();
-    }
-    return -1;
+    return (
+      this.stationInfo.autoReconnectMaxRetries ?? Configuration.getAutoReconnectMaxRetries() ?? -1
+    );
   }
 
   // 0 for disabling
   private getRegistrationMaxRetries(): number | undefined {
-    if (!Utils.isUndefined(this.stationInfo.registrationMaxRetries)) {
-      return this.stationInfo.registrationMaxRetries;
-    }
-    return -1;
+    return this.stationInfo.registrationMaxRetries ?? -1;
   }
 
   private getPowerDivider(): number {
-    let powerDivider = this.getNumberOfConnectors();
+    let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors();
     if (this.stationInfo?.powerSharedByConnectors) {
       powerDivider = this.getNumberOfRunningTransactions();
     }
@@ -1956,8 +2120,8 @@ export class ChargingStation {
       case CurrentType.AC:
         return ACElectricUtils.amperagePerPhaseFromPower(
           this.getNumberOfPhases(stationInfo),
-          maximumPower / this.getNumberOfConnectors(),
-          this.getVoltageOut(stationInfo)
+          maximumPower / (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
+          this.getVoltageOut(stationInfo),
         );
       case CurrentType.DC:
         return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo));
@@ -1966,19 +2130,13 @@ export class ChargingStation {
 
   private getAmperageLimitation(): number | undefined {
     if (
-      Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      ChargingStationConfigurationUtils.getConfigurationKey(
-        this,
-        this.stationInfo.amperageLimitationOcppKey
-      )
+      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)
     ) {
       return (
-        Utils.convertToInt(
-          ChargingStationConfigurationUtils.getConfigurationKey(
-            this,
-            this.stationInfo.amperageLimitationOcppKey
-          )?.value
-        ) / ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
+        convertToInt(
+          getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)?.value,
+        ) / getAmperageLimitationUnitDivider(this.stationInfo)
       );
     }
   }
@@ -1997,30 +2155,31 @@ export class ChargingStation {
     // Start heartbeat
     this.startHeartbeat();
     // Initialize connectors status
-    for (const connectorId of this.connectors.keys()) {
-      let connectorStatus: ConnectorStatusEnum | undefined;
-      if (connectorId === 0) {
-        continue;
-      } else if (
-        !this.getConnectorStatus(connectorId)?.status &&
-        (this.isChargingStationAvailable() === false ||
-          this.isConnectorAvailable(connectorId) === false)
-      ) {
-        connectorStatus = ConnectorStatusEnum.Unavailable;
-      } else if (
-        !this.getConnectorStatus(connectorId)?.status &&
-        this.getConnectorStatus(connectorId)?.bootStatus
-      ) {
-        // Set boot status in template at startup
-        connectorStatus = this.getConnectorStatus(connectorId)?.bootStatus;
-      } else if (this.getConnectorStatus(connectorId)?.status) {
-        // Set previous status at startup
-        connectorStatus = this.getConnectorStatus(connectorId)?.status;
-      } else {
-        // Set default status
-        connectorStatus = ConnectorStatusEnum.Available;
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+            const connectorBootStatus = getBootConnectorStatus(this, connectorId, connectorStatus);
+            await OCPPServiceUtils.sendAndSetConnectorStatus(
+              this,
+              connectorId,
+              connectorBootStatus,
+              evseId,
+            );
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0) {
+          const connectorBootStatus = getBootConnectorStatus(
+            this,
+            connectorId,
+            this.getConnectorStatus(connectorId)!,
+          );
+          await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorBootStatus);
+        }
       }
-      await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorStatus);
     }
     if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
       await this.ocppRequestService.requestHandler<
@@ -2033,14 +2192,14 @@ export class ChargingStation {
     }
 
     // Start the ATG
-    if (this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true) {
+    if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
       this.startAutomaticTransactionGenerator();
     }
     this.wsConnectionRestarted === true && this.flushMessageBuffer();
   }
 
   private async stopMessageSequence(
-    reason: StopTransactionReason = StopTransactionReason.NONE
+    reason: StopTransactionReason = StopTransactionReason.NONE,
   ): Promise<void> {
     // Stop WebSocket ping
     this.stopWebSocketPing();
@@ -2052,57 +2211,75 @@ export class ChargingStation {
     } else {
       await this.stopRunningTransactions(reason);
     }
-    for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0) {
-        await this.ocppRequestService.requestHandler<
-          StatusNotificationRequest,
-          StatusNotificationResponse
-        >(
-          this,
-          RequestCommand.STATUS_NOTIFICATION,
-          OCPPServiceUtils.buildStatusNotificationRequest(
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+            await this.ocppRequestService.requestHandler<
+              StatusNotificationRequest,
+              StatusNotificationResponse
+            >(
+              this,
+              RequestCommand.STATUS_NOTIFICATION,
+              OCPPServiceUtils.buildStatusNotificationRequest(
+                this,
+                connectorId,
+                ConnectorStatusEnum.Unavailable,
+                evseId,
+              ),
+            );
+            delete connectorStatus?.status;
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0) {
+          await this.ocppRequestService.requestHandler<
+            StatusNotificationRequest,
+            StatusNotificationResponse
+          >(
             this,
-            connectorId,
-            ConnectorStatusEnum.Unavailable
-          )
-        );
-        delete this.getConnectorStatus(connectorId)?.status;
+            RequestCommand.STATUS_NOTIFICATION,
+            OCPPServiceUtils.buildStatusNotificationRequest(
+              this,
+              connectorId,
+              ConnectorStatusEnum.Unavailable,
+            ),
+          );
+          delete this.getConnectorStatus(connectorId)?.status;
+        }
       }
     }
   }
 
   private startWebSocketPing(): void {
-    const webSocketPingInterval: number = ChargingStationConfigurationUtils.getConfigurationKey(
+    const webSocketPingInterval: number = getConfigurationKey(
       this,
-      StandardParametersKey.WebSocketPingInterval
+      StandardParametersKey.WebSocketPingInterval,
     )
-      ? Utils.convertToInt(
-          ChargingStationConfigurationUtils.getConfigurationKey(
-            this,
-            StandardParametersKey.WebSocketPingInterval
-          )?.value
-        )
+      ? convertToInt(getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value)
       : 0;
     if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
       this.webSocketPingSetInterval = setInterval(() => {
         if (this.isWebSocketConnectionOpened() === true) {
           this.wsConnection?.ping();
         }
-      }, webSocketPingInterval * 1000);
+      }, secondsToMilliseconds(webSocketPingInterval));
       logger.info(
-        `${this.logPrefix()} WebSocket ping started every ${Utils.formatDurationSeconds(
-          webSocketPingInterval
-        )}`
+        `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
+          webSocketPingInterval,
+        )}`,
       );
     } else if (this.webSocketPingSetInterval) {
       logger.info(
-        `${this.logPrefix()} WebSocket ping already started every ${Utils.formatDurationSeconds(
-          webSocketPingInterval
-        )}`
+        `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds(
+          webSocketPingInterval,
+        )}`,
       );
     } else {
       logger.error(
-        `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval}, not starting the WebSocket ping`
+        `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval}, not starting the WebSocket ping`,
       );
     }
   }
@@ -2115,30 +2292,41 @@ export class ChargingStation {
   }
 
   private getConfiguredSupervisionUrl(): URL {
+    let configuredSupervisionUrl: string;
     const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls();
-    if (Utils.isNotEmptyArray(supervisionUrls)) {
+    if (isNotEmptyArray(supervisionUrls)) {
       let configuredSupervisionUrlIndex: number;
       switch (Configuration.getSupervisionUrlDistribution()) {
         case SupervisionUrlDistribution.RANDOM:
-          configuredSupervisionUrlIndex = Math.floor(Utils.secureRandom() * supervisionUrls.length);
+          configuredSupervisionUrlIndex = Math.floor(
+            secureRandom() * (supervisionUrls as string[]).length,
+          );
           break;
         case SupervisionUrlDistribution.ROUND_ROBIN:
         case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
         default:
           Object.values(SupervisionUrlDistribution).includes(
-            Configuration.getSupervisionUrlDistribution()
+            Configuration.getSupervisionUrlDistribution()!,
           ) === false &&
             logger.error(
+              // eslint-disable-next-line @typescript-eslint/no-base-to-string
               `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${
                 SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
-              }`
+              }`,
             );
-          configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
+          configuredSupervisionUrlIndex = (this.index - 1) % (supervisionUrls as string[]).length;
           break;
       }
-      return new URL(supervisionUrls[configuredSupervisionUrlIndex]);
+      configuredSupervisionUrl = (supervisionUrls as string[])[configuredSupervisionUrlIndex];
+    } else {
+      configuredSupervisionUrl = supervisionUrls as string;
+    }
+    if (isNotEmptyString(configuredSupervisionUrl)) {
+      return new URL(configuredSupervisionUrl);
     }
-    return new URL(supervisionUrls as string);
+    const errorMsg = 'No supervision url(s) configured';
+    logger.error(`${this.logPrefix()} ${errorMsg}`);
+    throw new BaseError(`${errorMsg}`);
   }
 
   private stopHeartbeat(): void {
@@ -2165,52 +2353,46 @@ export class ChargingStation {
     // Stop heartbeat
     this.stopHeartbeat();
     // Stop the ATG if needed
-    if (this.automaticTransactionGenerator?.configuration?.stopOnConnectionFailure === true) {
+    if (this.getAutomaticTransactionGeneratorConfiguration().stopOnConnectionFailure === true) {
       this.stopAutomaticTransactionGenerator();
     }
     if (
-      this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries() ||
+      this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries()! ||
       this.getAutoReconnectMaxRetries() === -1
     ) {
-      this.autoReconnectRetryCount++;
+      ++this.autoReconnectRetryCount;
       const reconnectDelay = this.getReconnectExponentialDelay()
-        ? Utils.exponentialDelay(this.autoReconnectRetryCount)
-        : this.getConnectionTimeout() * 1000;
+        ? exponentialDelay(this.autoReconnectRetryCount)
+        : secondsToMilliseconds(this.getConnectionTimeout());
       const reconnectDelayWithdraw = 1000;
       const reconnectTimeout =
         reconnectDelay && reconnectDelay - reconnectDelayWithdraw > 0
           ? reconnectDelay - reconnectDelayWithdraw
           : 0;
       logger.error(
-        `${this.logPrefix()} WebSocket connection retry in ${Utils.roundTo(
+        `${this.logPrefix()} WebSocket connection retry in ${roundTo(
           reconnectDelay,
-          2
-        )}ms, timeout ${reconnectTimeout}ms`
+          2,
+        )}ms, timeout ${reconnectTimeout}ms`,
       );
-      await Utils.sleep(reconnectDelay);
+      await sleep(reconnectDelay);
       logger.error(
-        `${this.logPrefix()} WebSocket connection retry #${this.autoReconnectRetryCount.toString()}`
+        `${this.logPrefix()} WebSocket connection retry #${this.autoReconnectRetryCount.toString()}`,
       );
       this.openWSConnection(
         {
           ...(this.stationInfo?.wsOptions ?? {}),
           handshakeTimeout: reconnectTimeout,
         },
-        { closeOpened: true }
+        { closeOpened: true },
       );
       this.wsConnectionRestarted = true;
     } else if (this.getAutoReconnectMaxRetries() !== -1) {
       logger.error(
         `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${
           this.autoReconnectRetryCount
-        }) or retries disabled (${this.getAutoReconnectMaxRetries()})`
+        }) or retries disabled (${this.getAutoReconnectMaxRetries()})`,
       );
     }
   }
-
-  private getAutomaticTransactionGeneratorConfigurationFromTemplate():
-    | AutomaticTransactionGeneratorConfiguration
-    | undefined {
-    return this.getTemplateFromFile()?.AutomaticTransactionGenerator;
-  }
 }