refactor: revert internal exports
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index ad9789dc6ae187a94a108b202530c53f1539c80a..d730a2cb04f0bd5b37c825ce9b0a2def3b6fa16f 100644 (file)
 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
-import crypto from 'crypto';
-import fs from 'fs';
-import path from 'path';
-import { URL } from 'url';
-import { parentPort } from 'worker_threads';
+import crypto from 'node:crypto';
+import fs from 'node:fs';
+import path from 'node:path';
+import { URL } from 'node:url';
+import { parentPort } from 'node:worker_threads';
 
 import merge from 'just-merge';
 import WebSocket, { type RawData } from 'ws';
 
-import AuthorizedTagsCache from './AuthorizedTagsCache';
-import AutomaticTransactionGenerator from './AutomaticTransactionGenerator';
+import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator';
 import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
 import { ChargingStationUtils } from './ChargingStationUtils';
-import ChargingStationWorkerBroadcastChannel from './ChargingStationWorkerBroadcastChannel';
+import { ChargingStationWorkerBroadcastChannel } from './ChargingStationWorkerBroadcastChannel';
+import { IdTagsCache } from './IdTagsCache';
 import { MessageChannelUtils } from './MessageChannelUtils';
-import OCPP16IncomingRequestService from './ocpp/1.6/OCPP16IncomingRequestService';
-import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService';
-import OCPP16ResponseService from './ocpp/1.6/OCPP16ResponseService';
-import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils';
-import OCPP20IncomingRequestService from './ocpp/2.0/OCPP20IncomingRequestService';
-import OCPP20RequestService from './ocpp/2.0/OCPP20RequestService';
-import OCPP20ResponseService from './ocpp/2.0/OCPP20ResponseService';
-import type OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
-import type OCPPRequestService from './ocpp/OCPPRequestService';
-import { OCPPServiceUtils } from './ocpp/OCPPServiceUtils';
-import SharedLRUCache from './SharedLRUCache';
-import BaseError from '../exception/BaseError';
-import OCPPError from '../exception/OCPPError';
-import PerformanceStatistics from '../performance/PerformanceStatistics';
-import type { AutomaticTransactionGeneratorConfiguration } from '../types/AutomaticTransactionGenerator';
-import type { ChargingStationConfiguration } from '../types/ChargingStationConfiguration';
-import type { ChargingStationInfo } from '../types/ChargingStationInfo';
-import type { ChargingStationOcppConfiguration } from '../types/ChargingStationOcppConfiguration';
 import {
-  type ChargingStationTemplate,
-  CurrentType,
-  PowerUnits,
-  type WsOptions,
-} from '../types/ChargingStationTemplate';
-import { SupervisionUrlDistribution } from '../types/ConfigurationData';
-import type { ConnectorStatus } from '../types/ConnectorStatus';
-import { FileType } from '../types/FileType';
-import type { JsonType } from '../types/JsonType';
-import { ChargingProfile, ChargingRateUnitType } from '../types/ocpp/ChargingProfile';
-import {
-  ConnectorPhaseRotation,
-  StandardParametersKey,
-  SupportedFeatureProfiles,
-  VendorDefaultParametersKey,
-} from '../types/ocpp/Configuration';
-import { ConnectorStatusEnum } from '../types/ocpp/ConnectorStatusEnum';
-import { ErrorType } from '../types/ocpp/ErrorType';
-import { MessageType } from '../types/ocpp/MessageType';
-import { MeterValue, MeterValueMeasurand } from '../types/ocpp/MeterValues';
-import { OCPPVersion } from '../types/ocpp/OCPPVersion';
+  OCPP16IncomingRequestService,
+  OCPP16RequestService,
+  OCPP16ResponseService,
+  OCPP16ServiceUtils,
+  OCPP20IncomingRequestService,
+  OCPP20RequestService,
+  OCPP20ResponseService,
+  type OCPPIncomingRequestService,
+  type OCPPRequestService,
+  OCPPServiceUtils,
+} from './ocpp';
+import { SharedLRUCache } from './SharedLRUCache';
+import { BaseError, OCPPError } from '../exception';
+// import { PerformanceStatistics } from '../performance';
+import { PerformanceStatistics } from '../performance/PerformanceStatistics';
 import {
+  type AutomaticTransactionGeneratorConfiguration,
   AvailabilityType,
   type BootNotificationRequest,
+  type BootNotificationResponse,
   type CachedRequest,
+  type ChargingStationAutomaticTransactionGeneratorConfiguration,
+  type ChargingStationConfiguration,
+  type ChargingStationInfo,
+  type ChargingStationOcppConfiguration,
+  type ChargingStationTemplate,
+  type ConnectorStatus,
+  ConnectorStatusEnum,
+  CurrentType,
   type ErrorCallback,
+  type ErrorResponse,
+  ErrorType,
+  type EvseStatus,
+  type EvseStatusConfiguration,
+  FileType,
   FirmwareStatus,
   type FirmwareStatusNotificationRequest,
+  type FirmwareStatusNotificationResponse,
+  type FirmwareUpgrade,
   type HeartbeatRequest,
+  type HeartbeatResponse,
   type IncomingRequest,
-  IncomingRequestCommand,
+  type IncomingRequestCommand,
+  type JsonType,
+  MessageType,
+  type MeterValue,
+  MeterValueMeasurand,
   type MeterValuesRequest,
-  RequestCommand,
-  type ResponseCallback,
-  type StatusNotificationRequest,
-} from '../types/ocpp/Requests';
-import {
-  type BootNotificationResponse,
-  type ErrorResponse,
-  type FirmwareStatusNotificationResponse,
-  type HeartbeatResponse,
   type MeterValuesResponse,
+  OCPPVersion,
+  type OutgoingRequest,
+  PowerUnits,
   RegistrationStatusEnumType,
+  RequestCommand,
   type Response,
+  StandardParametersKey,
+  type StatusNotificationRequest,
   type StatusNotificationResponse,
-} from '../types/ocpp/Responses';
-import {
   StopTransactionReason,
   type StopTransactionRequest,
   type StopTransactionResponse,
-} from '../types/ocpp/Transaction';
-import { WSError, WebSocketCloseEventStatusCode } from '../types/WebSocket';
-import Configuration from '../utils/Configuration';
-import Constants from '../utils/Constants';
-import { ACElectricUtils, DCElectricUtils } from '../utils/ElectricUtils';
-import FileUtils from '../utils/FileUtils';
-import logger from '../utils/Logger';
-import Utils from '../utils/Utils';
-
-export default class ChargingStation {
+  SupervisionUrlDistribution,
+  SupportedFeatureProfiles,
+  VendorParametersKey,
+  type WSError,
+  WebSocketCloseEventStatusCode,
+  type WsOptions,
+} from '../types';
+import {
+  ACElectricUtils,
+  AsyncLock,
+  AsyncLockType,
+  Configuration,
+  Constants,
+  DCElectricUtils,
+  FileUtils,
+  Utils,
+  logger,
+} from '../utils';
+
+export class ChargingStation {
   public readonly index: number;
   public readonly templateFile: string;
   public stationInfo!: ChargingStationInfo;
   public started: boolean;
   public starting: boolean;
-  public authorizedTagsCache: AuthorizedTagsCache;
-  public automaticTransactionGenerator!: AutomaticTransactionGenerator;
-  public ocppConfiguration!: ChargingStationOcppConfiguration;
-  public wsConnection!: WebSocket;
+  public idTagsCache: IdTagsCache;
+  public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
+  public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
+  public wsConnection!: WebSocket | null;
   public readonly connectors: Map<number, ConnectorStatus>;
+  public readonly evses: Map<number, EvseStatus>;
   public readonly requests: Map<string, CachedRequest>;
-  public performanceStatistics!: PerformanceStatistics;
+  public performanceStatistics!: PerformanceStatistics | undefined;
   public heartbeatSetInterval!: NodeJS.Timeout;
   public ocppRequestService!: OCPPRequestService;
   public bootNotificationRequest!: BootNotificationRequest;
-  public bootNotificationResponse!: BootNotificationResponse | null;
+  public bootNotificationResponse!: BootNotificationResponse | undefined;
   public powerDivider!: number;
   private stopping: boolean;
   private configurationFile!: string;
   private configurationFileHash!: string;
   private connectorsConfigurationHash!: string;
+  private evsesConfigurationHash!: string;
   private ocppIncomingRequestService!: OCPPIncomingRequestService;
   private readonly messageBuffer: Set<string>;
   private configuredSupervisionUrl!: URL;
-  private configuredSupervisionUrlIndex!: number;
   private wsConnectionRestarted: boolean;
   private autoReconnectRetryCount: number;
-  private templateFileWatcher!: fs.FSWatcher;
+  private templateFileWatcher!: fs.FSWatcher | undefined;
+  private templateFileHash!: string;
   private readonly sharedLRUCache: SharedLRUCache;
   private webSocketPingSetInterval!: NodeJS.Timeout;
   private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
@@ -135,42 +140,48 @@ export default class ChargingStation {
     this.index = index;
     this.templateFile = templateFile;
     this.connectors = new Map<number, ConnectorStatus>();
+    this.evses = new Map<number, EvseStatus>();
     this.requests = new Map<string, CachedRequest>();
     this.messageBuffer = new Set<string>();
     this.sharedLRUCache = SharedLRUCache.getInstance();
-    this.authorizedTagsCache = AuthorizedTagsCache.getInstance();
+    this.idTagsCache = IdTagsCache.getInstance();
     this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
 
     this.initialize();
   }
 
+  public get hasEvses(): boolean {
+    return this.connectors.size === 0 && this.evses.size > 0;
+  }
+
   private get wsConnectionUrl(): URL {
     return new URL(
-      (this.getSupervisionUrlOcppConfiguration()
-        ? ChargingStationConfigurationUtils.getConfigurationKey(
-            this,
-            this.getSupervisionUrlOcppKey()
-          ).value
-        : this.configuredSupervisionUrl.href) +
-        '/' +
-        this.stationInfo.chargingStationId
+      `${
+        this.getSupervisionUrlOcppConfiguration() &&
+        Utils.isNotEmptyString(this.getSupervisionUrlOcppKey())
+          ? ChargingStationConfigurationUtils.getConfigurationKey(
+              this,
+              this.getSupervisionUrlOcppKey()
+            )?.value
+          : this.configuredSupervisionUrl.href
+      }/${this.stationInfo.chargingStationId}`
     );
   }
 
-  public logPrefix(): string {
+  public logPrefix = (): string => {
     return Utils.logPrefix(
       ` ${
-        this?.stationInfo?.chargingStationId ??
-        ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile())
+        (Utils.isNotEmptyString(this?.stationInfo?.chargingStationId)
+          ? this?.stationInfo?.chargingStationId
+          : ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile())) ??
+        'Error at building log prefix'
       } |`
     );
-  }
+  };
 
-  public hasAuthorizedTags(): boolean {
-    return !Utils.isEmptyArray(
-      this.authorizedTagsCache.getAuthorizedTags(
-        ChargingStationUtils.getAuthorizationFile(this.stationInfo)
-      )
+  public hasIdTags(): boolean {
+    return Utils.isNotEmptyArray(
+      this.idTagsCache.getIdTags(ChargingStationUtils.getIdTagsFile(this.stationInfo))
     );
   }
 
@@ -202,51 +213,96 @@ export default class ChargingStation {
     return this?.wsConnection?.readyState === WebSocket.OPEN;
   }
 
-  public getRegistrationStatus(): RegistrationStatusEnumType {
+  public getRegistrationStatus(): RegistrationStatusEnumType | undefined {
     return this?.bootNotificationResponse?.status;
   }
 
-  public isInUnknownState(): boolean {
+  public inUnknownState(): boolean {
     return Utils.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)
     );
   }
 
   public isChargingStationAvailable(): boolean {
-    return this.getConnectorStatus(0).availability === AvailabilityType.OPERATIVE;
+    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(id: number): boolean {
-    return id > 0 && this.getConnectorStatus(id).availability === AvailabilityType.OPERATIVE;
+  public isConnectorAvailable(connectorId: number): boolean {
+    return (
+      connectorId > 0 &&
+      this.getConnectorStatus(connectorId)?.availability === AvailabilityType.Operative
+    );
   }
 
   public getNumberOfConnectors(): number {
-    return this.connectors.get(0) ? this.connectors.size - 1 : this.connectors.size;
+    if (this.hasEvses) {
+      if (this.evses.size === 0) {
+        throw new BaseError('Evses not initialized, cannot get number of connectors');
+      }
+      let numberOfConnectors = 0;
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          numberOfConnectors += evseStatus.connectors.size;
+        }
+      }
+      return numberOfConnectors;
+    }
+    if (this.connectors.size === 0) {
+      throw new BaseError('Connectors not initialized, cannot get number of connectors');
+    }
+    return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size;
   }
 
-  public getConnectorStatus(id: number): ConnectorStatus | undefined {
-    return this.connectors.get(id);
+  public getNumberOfEvses(): number {
+    if (this.evses.size === 0) {
+      throw new BaseError('Evses not initialized, cannot get number of evses');
+    }
+    return this.evses.has(0) ? this.evses.size - 1 : this.evses.size;
+  }
+
+  public getConnectorStatus(connectorId: number): ConnectorStatus | undefined {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        if (evseStatus.connectors.has(connectorId)) {
+          return evseStatus.connectors.get(connectorId);
+        }
+      }
+      return undefined;
+    }
+    return this.connectors.get(connectorId);
   }
 
   public getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType {
-    return (stationInfo ?? this.stationInfo).currentOutType ?? CurrentType.AC;
+    return (stationInfo ?? this.stationInfo)?.currentOutType ?? CurrentType.AC;
   }
 
   public getOcppStrictCompliance(): boolean {
@@ -256,48 +312,85 @@ export default class ChargingStation {
   public getVoltageOut(stationInfo?: ChargingStationInfo): number | undefined {
     const defaultVoltageOut = ChargingStationUtils.getDefaultVoltageOut(
       this.getCurrentOutType(stationInfo),
-      this.templateFile,
-      this.logPrefix()
+      this.logPrefix(),
+      this.templateFile
     );
-    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;
   }
 
   public getConnectorMaximumAvailablePower(connectorId: number): number {
     let connectorAmperageLimitationPowerLimit: number;
     if (
       !Utils.isNullOrUndefined(this.getAmperageLimitation()) &&
-      this.getAmperageLimitation() < this.stationInfo.maximumAmperage
+      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())) /
         this.powerDivider;
     }
     const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
-    const connectorChargingProfilePowerLimit = this.getChargingProfilePowerLimit(connectorId);
+    const connectorChargingProfilesPowerLimit =
+      ChargingStationUtils.getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId);
     return Math.min(
       isNaN(connectorMaximumPower) ? Infinity : connectorMaximumPower,
       isNaN(connectorAmperageLimitationPowerLimit)
         ? Infinity
         : connectorAmperageLimitationPowerLimit,
-      isNaN(connectorChargingProfilePowerLimit) ? Infinity : connectorChargingProfilePowerLimit
+      isNaN(connectorChargingProfilesPowerLimit) ? Infinity : connectorChargingProfilesPowerLimit
     );
   }
 
   public getTransactionIdTag(transactionId: number): string | undefined {
-    for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0 && this.getConnectorStatus(connectorId).transactionId === transactionId) {
-        return this.getConnectorStatus(connectorId).transactionIdTag;
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.transactionId === transactionId) {
+            return connectorStatus.transactionIdTag;
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (
+          connectorId > 0 &&
+          this.getConnectorStatus(connectorId)?.transactionId === transactionId
+        ) {
+          return this.getConnectorStatus(connectorId)?.transactionIdTag;
+        }
+      }
+    }
+  }
+
+  public 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;
   }
 
   public getOutOfOrderEndMeterValues(): boolean {
@@ -329,28 +422,38 @@ export default class ChargingStation {
   }
 
   public getConnectorIdByTransactionId(transactionId: number): number | undefined {
-    for (const connectorId of this.connectors.keys()) {
-      if (
-        connectorId > 0 &&
-        this.getConnectorStatus(connectorId)?.transactionId === transactionId
-      ) {
-        return connectorId;
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (connectorStatus.transactionId === transactionId) {
+            return connectorId;
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (
+          connectorId > 0 &&
+          this.getConnectorStatus(connectorId)?.transactionId === transactionId
+        ) {
+          return connectorId;
+        }
       }
     }
   }
 
   public getEnergyActiveImportRegisterByTransactionId(
     transactionId: number,
-    meterStop = false
+    rounded = false
   ): number {
     return this.getEnergyActiveImportRegister(
       this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)),
-      meterStop
+      rounded
     );
   }
 
-  public getEnergyActiveImportRegisterByConnectorId(connectorId: number): number {
-    return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId));
+  public getEnergyActiveImportRegisterByConnectorId(connectorId: number, rounded = false): number {
+    return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId), rounded);
   }
 
   public getAuthorizeRemoteTxRequests(): boolean {
@@ -371,12 +474,49 @@ export default class ChargingStation {
     return localAuthListEnabled ? Utils.convertToBoolean(localAuthListEnabled.value) : false;
   }
 
-  public startHeartbeat(): void {
+  public getHeartbeatInterval(): number {
+    const HeartbeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
+      this,
+      StandardParametersKey.HeartbeatInterval
+    );
+    if (HeartbeatInterval) {
+      return Utils.convertToInt(HeartbeatInterval.value) * 1000;
+    }
+    const HeartBeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
+      this,
+      StandardParametersKey.HeartBeatInterval
+    );
+    if (HeartBeatInterval) {
+      return Utils.convertToInt(HeartBeatInterval.value) * 1000;
+    }
+    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;
+  }
+
+  public setSupervisionUrl(url: string): void {
     if (
-      this.getHeartbeatInterval() &&
-      this.getHeartbeatInterval() > 0 &&
-      !this.heartbeatSetInterval
+      this.getSupervisionUrlOcppConfiguration() &&
+      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey())
     ) {
+      ChargingStationConfigurationUtils.setConfigurationKeyValue(
+        this,
+        this.getSupervisionUrlOcppKey(),
+        url
+      );
+    } else {
+      this.stationInfo.supervisionUrls = url;
+      this.saveStationInfo();
+      this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
+    }
+  }
+
+  public startHeartbeat(): void {
+    if (this.getHeartbeatInterval() > 0 && !this.heartbeatSetInterval) {
       this.heartbeatSetInterval = setInterval(() => {
         this.ocppRequestService
           .requestHandler<HeartbeatRequest, HeartbeatResponse>(this, RequestCommand.HEARTBEAT)
@@ -388,23 +528,19 @@ export default class ChargingStation {
           });
       }, this.getHeartbeatInterval());
       logger.info(
-        this.logPrefix() +
-          ' Heartbeat started every ' +
-          Utils.formatDurationMilliSeconds(this.getHeartbeatInterval())
+        `${this.logPrefix()} Heartbeat started every ${Utils.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 ${Utils.formatDurationMilliSeconds(
+          this.getHeartbeatInterval()
+        )}`
       );
     } else {
       logger.error(
-        `${this.logPrefix()} Heartbeat interval set to ${
-          this.getHeartbeatInterval()
-            ? Utils.formatDurationMilliSeconds(this.getHeartbeatInterval())
-            : this.getHeartbeatInterval()
-        }, not starting the heartbeat`
+        `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat`
       );
     }
   }
@@ -426,27 +562,27 @@ export default 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.toString()}`
       );
       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.toString()}`
       );
       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 &&
-      !this.getConnectorStatus(connectorId)?.transactionId
+      Utils.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;
     }
@@ -465,7 +601,7 @@ export default class ChargingStation {
             RequestCommand.METER_VALUES,
             {
               connectorId,
-              transactionId: this.getConnectorStatus(connectorId).transactionId,
+              transactionId: this.getConnectorStatus(connectorId)?.transactionId,
               meterValue: [meterValue],
             }
           )
@@ -480,49 +616,51 @@ export default class ChargingStation {
       logger.error(
         `${this.logPrefix()} Charging station ${
           StandardParametersKey.MeterValueSampleInterval
-        } configuration set to ${
-          interval ? Utils.formatDurationMilliSeconds(interval) : interval
-        }, not sending MeterValues`
+        } configuration set to ${interval}, not sending MeterValues`
       );
     }
   }
 
+  public stopMeterValues(connectorId: number) {
+    if (this.getConnectorStatus(connectorId)?.transactionSetInterval) {
+      clearInterval(this.getConnectorStatus(connectorId)?.transactionSetInterval);
+    }
+  }
+
   public start(): void {
     if (this.started === false) {
       if (this.starting === false) {
         this.starting = true;
         if (this.getEnableStatistics() === true) {
-          this.performanceStatistics.start();
+          this.performanceStatistics?.start();
         }
         this.openWSConnection();
         // Monitor charging station template file
         this.templateFileWatcher = FileUtils.watchJsonFile(
-          this.logPrefix(),
-          FileType.ChargingStationTemplate,
           this.templateFile,
-          null,
+          FileType.ChargingStationTemplate,
+          this.logPrefix(),
+          undefined,
           (event, filename): void => {
-            if (filename && event === 'change') {
+            if (Utils.isNotEmptyString(filename) && event === 'change') {
               try {
                 logger.debug(
                   `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
                     this.templateFile
                   } file have changed, reload`
                 );
-                this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
+                this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
                 // Initialize
                 this.initialize();
                 // Restart the ATG
                 this.stopAutomaticTransactionGenerator();
-                if (
-                  this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true
-                ) {
+                if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
                   this.startAutomaticTransactionGenerator();
                 }
                 if (this.getEnableStatistics() === true) {
-                  this.performanceStatistics.restart();
+                  this.performanceStatistics?.restart();
                 } else {
-                  this.performanceStatistics.stop();
+                  this.performanceStatistics?.stop();
                 }
                 // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
               } catch (error) {
@@ -535,7 +673,7 @@ export default class ChargingStation {
           }
         );
         this.started = true;
-        parentPort.postMessage(MessageChannelUtils.buildStartedMessage(this));
+        parentPort?.postMessage(MessageChannelUtils.buildStartedMessage(this));
         this.starting = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already starting...`);
@@ -552,14 +690,15 @@ export default class ChargingStation {
         await this.stopMessageSequence(reason);
         this.closeWSConnection();
         if (this.getEnableStatistics() === true) {
-          this.performanceStatistics.stop();
+          this.performanceStatistics?.stop();
         }
         this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
-        this.templateFileWatcher.close();
-        this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
-        this.bootNotificationResponse = null;
+        this.templateFileWatcher?.close();
+        this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
+        delete this.bootNotificationResponse;
         this.started = false;
-        parentPort.postMessage(MessageChannelUtils.buildStoppedMessage(this));
+        this.saveConfiguration();
+        parentPort?.postMessage(MessageChannelUtils.buildStoppedMessage(this));
         this.stopping = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already stopping...`);
@@ -582,26 +721,11 @@ export default class ChargingStation {
     }
   }
 
-  public resetConnectorStatus(connectorId: number): void {
-    this.getConnectorStatus(connectorId).idTagLocalAuthorized = false;
-    this.getConnectorStatus(connectorId).idTagAuthorized = false;
-    this.getConnectorStatus(connectorId).transactionRemoteStarted = false;
-    this.getConnectorStatus(connectorId).transactionStarted = false;
-    delete this.getConnectorStatus(connectorId).localAuthorizeIdTag;
-    delete this.getConnectorStatus(connectorId).authorizeIdTag;
-    delete this.getConnectorStatus(connectorId).transactionId;
-    delete this.getConnectorStatus(connectorId).transactionIdTag;
-    this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;
-    delete this.getConnectorStatus(connectorId).transactionBeginMeterValue;
-    this.stopMeterValues(connectorId);
-    parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
-  }
-
-  public hasFeatureProfile(featureProfile: SupportedFeatureProfiles): boolean {
+  public hasFeatureProfile(featureProfile: SupportedFeatureProfiles): boolean | undefined {
     return ChargingStationConfigurationUtils.getConfigurationKey(
       this,
       StandardParametersKey.SupportedFeatureProfiles
-    )?.value.includes(featureProfile);
+    )?.value?.includes(featureProfile);
   }
 
   public bufferMessage(message: string): void {
@@ -616,8 +740,7 @@ export default class ChargingStation {
     }
   ): void {
     options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
-    params.closeOpened = params?.closeOpened ?? false;
-    params.terminateOpened = params?.terminateOpened ?? false;
+    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`
@@ -636,18 +759,6 @@ export default class ChargingStation {
     if (params?.terminateOpened) {
       this.terminateWSConnection();
     }
-    const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
-    let protocol: string;
-    switch (ocppVersion) {
-      case OCPPVersion.VERSION_16:
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        protocol = 'ocpp' + ocppVersion;
-        break;
-      default:
-        this.handleUnsupportedVersion(ocppVersion);
-        break;
-    }
 
     if (this.isWebSocketConnectionOpened() === true) {
       logger.warn(
@@ -660,7 +771,11 @@ export default class ChargingStation {
       `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`
     );
 
-    this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
+    this.wsConnection = new WebSocket(
+      this.wsConnectionUrl,
+      `ocpp${this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16}`,
+      options
+    );
 
     // Handle WebSocket message
     this.wsConnection.on(
@@ -687,46 +802,52 @@ export default class ChargingStation {
 
   public closeWSConnection(): void {
     if (this.isWebSocketConnectionOpened() === true) {
-      this.wsConnection.close();
+      this.wsConnection?.close();
       this.wsConnection = null;
     }
   }
 
-  public startAutomaticTransactionGenerator(
-    connectorIds?: number[],
-    automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration
-  ): void {
-    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(
-      automaticTransactionGeneratorConfiguration ??
-        this.getAutomaticTransactionGeneratorConfigurationFromTemplate(),
-      this
-    );
-    if (!Utils.isEmptyArray(connectorIds)) {
+  public getAutomaticTransactionGeneratorConfiguration():
+    | AutomaticTransactionGeneratorConfiguration
+    | undefined {
+    const automaticTransactionGeneratorConfigurationFromFile =
+      this.getConfigurationFromFile()?.automaticTransactionGenerator;
+    if (automaticTransactionGeneratorConfigurationFromFile) {
+      return automaticTransactionGeneratorConfigurationFromFile;
+    }
+    return this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+  }
+
+  public startAutomaticTransactionGenerator(connectorIds?: number[]): void {
+    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this);
+    if (Utils.isNotEmptyArray(connectorIds)) {
       for (const connectorId of connectorIds) {
-        this.automaticTransactionGenerator.startConnector(connectorId);
+        this.automaticTransactionGenerator?.startConnector(connectorId);
       }
     } else {
-      this.automaticTransactionGenerator.start();
+      this.automaticTransactionGenerator?.start();
     }
-    parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    this.saveChargingStationAutomaticTransactionGeneratorConfiguration();
+    parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
   }
 
   public stopAutomaticTransactionGenerator(connectorIds?: number[]): void {
-    if (!Utils.isEmptyArray(connectorIds)) {
+    if (Utils.isNotEmptyArray(connectorIds)) {
       for (const connectorId of connectorIds) {
         this.automaticTransactionGenerator?.stopConnector(connectorId);
       }
     } else {
       this.automaticTransactionGenerator?.stop();
     }
-    parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    this.saveChargingStationAutomaticTransactionGeneratorConfiguration();
+    parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
   }
 
   public async stopTransactionOnConnector(
     connectorId: number,
     reason = StopTransactionReason.NONE
   ): Promise<StopTransactionResponse> {
-    const transactionId = this.getConnectorStatus(connectorId).transactionId;
+    const transactionId = this.getConnectorStatus(connectorId)?.transactionId;
     if (
       this.getBeginEndMeterValues() === true &&
       this.getOcppStrictCompliance() === true &&
@@ -761,11 +882,24 @@ export default class ChargingStation {
 
   private flushMessageBuffer(): void {
     if (this.messageBuffer.size > 0) {
-      this.messageBuffer.forEach((message) => {
-        // TODO: evaluate the need to track performance
-        this.wsConnection.send(message);
+      for (const message of this.messageBuffer.values()) {
+        let beginId: string;
+        let commandName: RequestCommand;
+        const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse;
+        const isRequest = messageType === MessageType.CALL_MESSAGE;
+        if (isRequest) {
+          [, , commandName] = JSON.parse(message) as OutgoingRequest;
+          beginId = PerformanceStatistics.beginMeasure(commandName);
+        }
+        this.wsConnection?.send(message);
+        isRequest && PerformanceStatistics.endMeasure(commandName, beginId);
+        logger.debug(
+          `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString(
+            messageType
+          )} payload sent: ${message}`
+        );
         this.messageBuffer.delete(message);
-      });
+      }
     }
   }
 
@@ -774,14 +908,14 @@ export default class ChargingStation {
   }
 
   private getSupervisionUrlOcppKey(): string {
-    return this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl;
+    return this.stationInfo.supervisionUrlOcppKey ?? VendorParametersKey.ConnectionUrl;
   }
 
-  private getTemplateFromFile(): ChargingStationTemplate | null {
-    let template: ChargingStationTemplate = null;
+  private getTemplateFromFile(): ChargingStationTemplate | undefined {
+    let template: ChargingStationTemplate;
     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);
@@ -794,43 +928,34 @@ export default class ChargingStation {
           .update(JSON.stringify(template))
           .digest('hex');
         this.sharedLRUCache.setChargingStationTemplate(template);
+        this.templateFileHash = template.templateHash;
       }
     } catch (error) {
       FileUtils.handleFileException(
-        this.logPrefix(),
-        FileType.ChargingStationTemplate,
         this.templateFile,
-        error as NodeJS.ErrnoException
+        FileType.ChargingStationTemplate,
+        error as NodeJS.ErrnoException,
+        this.logPrefix()
       );
     }
     return template;
   }
 
   private getStationInfoFromTemplate(): ChargingStationInfo {
-    const stationTemplate: ChargingStationTemplate = 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);
-    }
-    // Deprecation template keys section
-    ChargingStationUtils.warnDeprecatedTemplateKey(
+    const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile();
+    ChargingStationUtils.checkTemplateFile(stationTemplate, this.logPrefix(), this.templateFile);
+    ChargingStationUtils.warnTemplateKeysDeprecation(
       stationTemplate,
-      'supervisionUrl',
-      this.templateFile,
       this.logPrefix(),
-      "Use 'supervisionUrls' instead"
-    );
-    ChargingStationUtils.convertDeprecatedTemplateKey(
-      stationTemplate,
-      'supervisionUrl',
-      'supervisionUrls'
+      this.templateFile
     );
+    if (stationTemplate?.Connectors) {
+      ChargingStationUtils.checkConnectorsConfiguration(
+        stationTemplate,
+        this.logPrefix(),
+        this.templateFile
+      );
+    }
     const stationInfo: ChargingStationInfo =
       ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
     stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate);
@@ -838,26 +963,26 @@ export default class ChargingStation {
       this.index,
       stationTemplate
     );
-    stationInfo.ocppVersion = stationTemplate.ocppVersion ?? OCPPVersion.VERSION_16;
+    stationInfo.ocppVersion = stationTemplate?.ocppVersion ?? OCPPVersion.VERSION_16;
     ChargingStationUtils.createSerialNumber(stationTemplate, stationInfo);
-    if (!Utils.isEmptyArray(stationTemplate.power)) {
+    if (Utils.isNotEmptyArray(stationTemplate?.power)) {
       stationTemplate.power = stationTemplate.power as number[];
       const powerArrayRandomIndex = Math.floor(Utils.secureRandom() * stationTemplate.power.length);
       stationInfo.maximumPower =
-        stationTemplate.powerUnit === PowerUnits.KILO_WATT
+        stationTemplate?.powerUnit === PowerUnits.KILO_WATT
           ? stationTemplate.power[powerArrayRandomIndex] * 1000
           : stationTemplate.power[powerArrayRandomIndex];
     } else {
-      stationTemplate.power = stationTemplate.power as number;
+      stationTemplate.power = stationTemplate?.power as number;
       stationInfo.maximumPower =
-        stationTemplate.powerUnit === PowerUnits.KILO_WATT
+        stationTemplate?.powerUnit === PowerUnits.KILO_WATT
           ? stationTemplate.power * 1000
           : stationTemplate.power;
     }
     stationInfo.firmwareVersionPattern =
-      stationTemplate.firmwareVersionPattern ?? Constants.SEMVER_PATTERN;
+      stationTemplate?.firmwareVersionPattern ?? Constants.SEMVER_PATTERN;
     if (
-      stationInfo.firmwareVersion &&
+      Utils.isNotEmptyString(stationInfo.firmwareVersion) &&
       new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion) === false
     ) {
       logger.warn(
@@ -866,64 +991,40 @@ export default class ChargingStation {
         } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
       );
     }
-    stationInfo.firmwareUpgrade = merge(
+    stationInfo.firmwareUpgrade = merge<FirmwareUpgrade>(
       {
+        versionUpgrade: {
+          step: 1,
+        },
         reset: true,
       },
-      stationTemplate.firmwareUpgrade ?? {}
+      stationTemplate?.firmwareUpgrade ?? {}
     );
-    stationInfo.resetTime = stationTemplate.resetTime
+    stationInfo.resetTime = !Utils.isNullOrUndefined(stationTemplate?.resetTime)
       ? stationTemplate.resetTime * 1000
       : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
-    const configuredMaxConnectors =
-      ChargingStationUtils.getConfiguredNumberOfConnectors(stationTemplate);
-    ChargingStationUtils.checkConfiguredMaxConnectors(
-      configuredMaxConnectors,
-      this.templateFile,
-      this.logPrefix()
-    );
-    const templateMaxConnectors =
-      ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate);
-    ChargingStationUtils.checkTemplateMaxConnectors(
-      templateMaxConnectors,
-      this.templateFile,
-      this.logPrefix()
-    );
-    if (
-      configuredMaxConnectors >
-        (stationTemplate?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) &&
-      !stationTemplate?.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;
-    }
-    // Build connectors if needed (FIXME: should be factored out)
-    this.initializeConnectors(stationInfo, configuredMaxConnectors, templateMaxConnectors);
     stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
-    ChargingStationUtils.createStationInfoHash(stationInfo);
     return stationInfo;
   }
 
-  private getStationInfoFromFile(): ChargingStationInfo | null {
-    let stationInfo: ChargingStationInfo = null;
-    this.getStationInfoPersistentConfiguration() &&
-      (stationInfo = this.getConfigurationFromFile()?.stationInfo ?? null);
-    stationInfo && ChargingStationUtils.createStationInfoHash(stationInfo);
+  private getStationInfoFromFile(): ChargingStationInfo | undefined {
+    let stationInfo: ChargingStationInfo | undefined;
+    if (this.getStationInfoPersistentConfiguration()) {
+      stationInfo = this.getConfigurationFromFile()?.stationInfo;
+      if (stationInfo) {
+        delete stationInfo?.infoHash;
+      }
+    }
     return stationInfo;
   }
 
   private getStationInfo(): ChargingStationInfo {
     const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate();
-    const stationInfoFromFile: ChargingStationInfo = this.getStationInfoFromFile();
-    // Priority: charging station info from template > charging station info from configuration file > charging station info attribute
+    const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile();
+    // Priority:
+    // 1. charging station info from template
+    // 2. charging station info from configuration file
     if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) {
-      if (this.stationInfo?.infoHash === stationInfoFromFile?.infoHash) {
-        return this.stationInfo;
-      }
       return stationInfoFromFile;
     }
     stationInfoFromFile &&
@@ -950,20 +1051,48 @@ export default class ChargingStation {
   }
 
   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 {
+    const stationTemplate = this.getTemplateFromFile();
+    ChargingStationUtils.checkTemplateFile(stationTemplate, this.logPrefix(), this.templateFile);
     this.configurationFile = path.join(
       path.dirname(this.templateFile.replace('station-templates', 'configurations')),
-      ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile()) + '.json'
+      `${ChargingStationUtils.getHashId(this.index, stationTemplate)}.json`
     );
+    const chargingStationConfiguration = this.getConfigurationFromFile();
+    const featureFlag = false;
+    if (
+      featureFlag &&
+      (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)
+    ) {
+      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 patchLevelIndex = match.length - 1;
+      match[patchLevelIndex] = (
+        Utils.convertToInt(match[patchLevelIndex]) +
+        this.stationInfo.firmwareUpgrade?.versionUpgrade?.step
+      ).toString();
+      this.stationInfo.firmwareVersion = match?.join('.');
+    }
     this.saveStationInfo();
-    // Avoid duplication of connectors related information in RAM
-    this.stationInfo?.Connectors && delete this.stationInfo.Connectors;
     this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
     if (this.getEnableStatistics() === true) {
       this.performanceStatistics = PerformanceStatistics.getInstance(
@@ -979,6 +1108,17 @@ export default class ChargingStation {
     // OCPP configuration
     this.ocppConfiguration = this.getOcppConfiguration();
     this.initializeOcppConfiguration();
+    this.initializeOcppServices();
+    if (this.stationInfo?.autoRegister === true) {
+      this.bootNotificationResponse = {
+        currentTime: new Date(),
+        interval: this.getHeartbeatInterval() / 1000,
+        status: RegistrationStatusEnumType.ACCEPTED,
+      };
+    }
+  }
+
+  private initializeOcppServices(): void {
     const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
     switch (ocppVersion) {
       case OCPPVersion.VERSION_16:
@@ -1000,31 +1140,6 @@ export default class ChargingStation {
         this.handleUnsupportedVersion(ocppVersion);
         break;
     }
-    if (this.stationInfo?.autoRegister === true) {
-      this.bootNotificationResponse = {
-        currentTime: new Date(),
-        interval: this.getHeartbeatInterval() / 1000,
-        status: RegistrationStatusEnumType.ACCEPTED,
-      };
-    }
-    if (
-      this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
-      this.stationInfo.firmwareVersion &&
-      this.stationInfo.firmwareVersionPattern
-    ) {
-      const versionStep = this.stationInfo.firmwareUpgrade?.versionUpgrade?.step ?? 1;
-      const patternGroup: number =
-        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 patchLevelIndex = match.length - 1;
-      match[patchLevelIndex] = (
-        Utils.convertToInt(match[patchLevelIndex]) + versionStep
-      ).toString();
-      this.stationInfo.firmwareVersion = match.join('.');
-    }
   }
 
   private initializeOcppConfiguration(): void {
@@ -1055,6 +1170,7 @@ export default class ChargingStation {
     }
     if (
       this.getSupervisionUrlOcppConfiguration() &&
+      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
       !ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
     ) {
       ChargingStationConfigurationUtils.addConfigurationKey(
@@ -1065,6 +1181,7 @@ export default class ChargingStation {
       );
     } else if (
       !this.getSupervisionUrlOcppConfiguration() &&
+      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
       ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
     ) {
       ChargingStationConfigurationUtils.deleteConfigurationKey(
@@ -1074,7 +1191,7 @@ export default class ChargingStation {
       );
     }
     if (
-      this.stationInfo.amperageLimitationOcppKey &&
+      Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
       !ChargingStationConfigurationUtils.getConfigurationKey(
         this,
         this.stationInfo.amperageLimitationOcppKey
@@ -1126,24 +1243,26 @@ export default class ChargingStation {
         StandardParametersKey.ConnectorPhaseRotation
       )
     ) {
-      const connectorPhaseRotation = [];
-      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}`);
+      const connectorsPhaseRotation: string[] = [];
+      if (this.hasEvses) {
+        for (const evseStatus of this.evses.values()) {
+          for (const connectorId of evseStatus.connectors.keys()) {
+            connectorsPhaseRotation.push(
+              ChargingStationUtils.getPhaseRotationValue(connectorId, this.getNumberOfPhases())
+            );
+          }
+        }
+      } else {
+        for (const connectorId of this.connectors.keys()) {
+          connectorsPhaseRotation.push(
+            ChargingStationUtils.getPhaseRotationValue(connectorId, this.getNumberOfPhases())
+          );
         }
       }
       ChargingStationConfigurationUtils.addConfigurationKey(
         this,
         StandardParametersKey.ConnectorPhaseRotation,
-        connectorPhaseRotation.toString()
+        connectorsPhaseRotation.toString()
       );
     }
     if (
@@ -1166,7 +1285,7 @@ export default class ChargingStation {
       ChargingStationConfigurationUtils.getConfigurationKey(
         this,
         StandardParametersKey.SupportedFeatureProfiles
-      )?.value.includes(SupportedFeatureProfiles.LocalAuthListManagement)
+      )?.value?.includes(SupportedFeatureProfiles.LocalAuthListManagement)
     ) {
       ChargingStationConfigurationUtils.addConfigurationKey(
         this,
@@ -1189,68 +1308,113 @@ export default class ChargingStation {
     this.saveOcppConfiguration();
   }
 
-  private initializeConnectors(
-    stationInfo: ChargingStationInfo,
-    configuredMaxConnectors: number,
-    templateMaxConnectors: number
-  ): 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 initializeConnectorsOrEvsesFromFile(configuration: ChargingStationConfiguration): void {
+    if (configuration?.connectorsStatus && !configuration?.evsesStatus) {
+      for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) {
+        this.connectors.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
+      }
+    } else if (configuration?.evsesStatus && !configuration?.connectorsStatus) {
+      for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) {
+        const evseStatus = Utils.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);
     }
-    if (!stationInfo?.Connectors[0]) {
+  }
+
+  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);
+    } else {
+      const errorMsg = `No connectors or evses defined in template file ${this.templateFile}`;
+      logger.error(`${this.logPrefix()} ${errorMsg}`);
+      throw new BaseError(errorMsg);
+    }
+  }
+
+  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 (!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) {
+    if (stationTemplate?.Connectors) {
+      const { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors } =
+        ChargingStationUtils.checkConnectorsConfiguration(
+          stationTemplate,
+          this.logPrefix(),
+          this.templateFile
+        );
       const connectorsConfigHash = crypto
         .createHash(Constants.DEFAULT_HASH_ALGORITHM)
-        .update(JSON.stringify(stationInfo?.Connectors) + configuredMaxConnectors.toString())
+        .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;
-        // Add connector Id 0
-        let lastConnector = '0';
-        for (lastConnector in stationInfo?.Connectors) {
-          const connectorStatus = stationInfo?.Connectors[lastConnector];
-          const lastConnectorId = Utils.convertToInt(lastConnector);
-          if (
-            lastConnectorId === 0 &&
-            this.getUseConnectorId0(stationInfo) === true &&
-            connectorStatus
-          ) {
-            this.checkStationInfoConnectorStatus(lastConnectorId, connectorStatus);
-            this.connectors.set(
-              lastConnectorId,
-              Utils.cloneObject<ConnectorStatus>(connectorStatus)
-            );
-            this.getConnectorStatus(lastConnectorId).availability = AvailabilityType.OPERATIVE;
-            if (Utils.isUndefined(this.getConnectorStatus(lastConnectorId)?.chargingProfiles)) {
-              this.getConnectorStatus(lastConnectorId).chargingProfiles = [];
-            }
-          }
-        }
-        // Generate all connectors
-        if ((stationInfo?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) > 0) {
-          for (let index = 1; index <= configuredMaxConnectors; index++) {
-            const randConnectorId = stationInfo?.randomConnectors
-              ? Utils.getRandomInteger(Utils.convertToInt(lastConnector), 1)
-              : index;
-            const connectorStatus = stationInfo?.Connectors[randConnectorId.toString()];
-            this.checkStationInfoConnectorStatus(randConnectorId, connectorStatus);
-            this.connectors.set(index, Utils.cloneObject<ConnectorStatus>(connectorStatus));
-            this.getConnectorStatus(index).availability = AvailabilityType.OPERATIVE;
-            if (Utils.isUndefined(this.getConnectorStatus(index)?.chargingProfiles)) {
-              this.getConnectorStatus(index).chargingProfiles = [];
+        if (templateMaxConnectors > 0) {
+          for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
+            if (
+              connectorId === 0 &&
+              (!stationTemplate?.Connectors[connectorId] ||
+                this.getUseConnectorId0(stationTemplate) === false)
+            ) {
+              continue;
             }
+            const templateConnectorId =
+              connectorId > 0 && stationTemplate?.randomConnectors
+                ? Utils.getRandomInteger(templateMaxAvailableConnectors, 1)
+                : connectorId;
+            const connectorStatus = stationTemplate?.Connectors[templateConnectorId];
+            ChargingStationUtils.checkStationInfoConnectorStatus(
+              templateConnectorId,
+              connectorStatus,
+              this.logPrefix(),
+              this.templateFile
+            );
+            this.connectors.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
           }
+          ChargingStationUtils.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`
+          );
         }
       }
     } else {
@@ -1260,41 +1424,75 @@ export default class ChargingStation {
         } with no connectors configuration defined, using already defined connectors`
       );
     }
-    // Initialize transaction attributes on connectors
-    for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0 && this.getConnectorStatus(connectorId).transactionStarted === true) {
-        logger.warn(
-          `${this.logPrefix()} Connector ${connectorId} at initialization has a transaction started: ${
-            this.getConnectorStatus(connectorId).transactionId
-          }`
-        );
-      }
-      if (
-        connectorId > 0 &&
-        (this.getConnectorStatus(connectorId).transactionStarted === undefined ||
-          this.getConnectorStatus(connectorId).transactionStarted === null)
-      ) {
-        this.initializeConnectorStatus(connectorId);
-      }
-    }
   }
 
-  private checkStationInfoConnectorStatus(
-    connectorId: number,
-    connectorStatus: ConnectorStatus
-  ): void {
-    if (!Utils.isNullOrUndefined(connectorStatus?.status)) {
+  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 (!stationTemplate?.Evses[0]) {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with no evse id 0 configuration`
+      );
+    }
+    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`
+      );
+    }
+    if (stationTemplate?.Evses) {
+      const evsesConfigHash = crypto
+        .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(stationTemplate?.Evses);
+        if (templateMaxEvses > 0) {
+          for (const evse in stationTemplate.Evses) {
+            const evseId = Utils.convertToInt(evse);
+            this.evses.set(evseId, {
+              connectors: ChargingStationUtils.buildConnectorsMap(
+                stationTemplate?.Evses[evse]?.Connectors,
+                this.logPrefix(),
+                this.templateFile
+              ),
+              availability: AvailabilityType.Operative,
+            });
+            ChargingStationUtils.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`
+          );
+        }
+      }
+    } else {
       logger.warn(
         `${this.logPrefix()} Charging station information from template ${
           this.templateFile
-        } with connector ${connectorId} status configuration defined, undefine it`
+        } with no evses configuration defined, using already defined evses`
       );
-      connectorStatus.status = undefined;
     }
   }
 
-  private getConfigurationFromFile(): ChargingStationConfiguration | null {
-    let configuration: ChargingStationConfiguration = null;
+  private getConfigurationFromFile(): ChargingStationConfiguration | undefined {
+    let configuration: ChargingStationConfiguration | undefined;
     if (this.configurationFile && fs.existsSync(this.configurationFile)) {
       try {
         if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
@@ -1308,48 +1506,114 @@ export default class ChargingStation {
             fs.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(
-          this.logPrefix(),
-          FileType.ChargingStationConfiguration,
           this.configurationFile,
-          error as NodeJS.ErrnoException
+          FileType.ChargingStationConfiguration,
+          error as NodeJS.ErrnoException,
+          this.logPrefix()
         );
       }
     }
     return configuration;
   }
 
-  private saveConfiguration(): void {
+  private saveChargingStationAutomaticTransactionGeneratorConfiguration(
+    stationTemplate?: ChargingStationTemplate
+  ): void {
+    this.saveConfiguration({
+      automaticTransactionGenerator: (stationTemplate ?? this.getTemplateFromFile())
+        .AutomaticTransactionGenerator,
+      ...(!Utils.isNullOrUndefined(this.automaticTransactionGenerator?.connectorsStatus) && {
+        automaticTransactionGeneratorStatuses: [
+          ...this.automaticTransactionGenerator.connectorsStatus.values(),
+        ],
+      }),
+    });
+  }
+
+  private saveConnectorsStatus() {
+    this.saveConfiguration();
+  }
+
+  private saveEvsesStatus() {
+    this.saveConfiguration();
+  }
+
+  private saveConfiguration(
+    chargingStationAutomaticTransactionGeneratorConfiguration?: ChargingStationAutomaticTransactionGeneratorConfiguration
+  ): void {
     if (this.configurationFile) {
       try {
         if (!fs.existsSync(path.dirname(this.configurationFile))) {
           fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
         }
-        const configurationData: ChargingStationConfiguration =
-          this.getConfigurationFromFile() ?? {};
-        this.ocppConfiguration?.configurationKey &&
-          (configurationData.configurationKey = this.ocppConfiguration.configurationKey);
-        this.stationInfo && (configurationData.stationInfo = this.stationInfo);
+        let configurationData: ChargingStationConfiguration =
+          Utils.cloneObject<ChargingStationConfiguration>(this.getConfigurationFromFile()) ?? {};
+        if (this.getStationInfoPersistentConfiguration() && this.stationInfo) {
+          configurationData.stationInfo = this.stationInfo;
+        }
+        if (this.getOcppPersistentConfiguration() && this.ocppConfiguration?.configurationKey) {
+          configurationData.configurationKey = this.ocppConfiguration.configurationKey;
+        }
+        if (chargingStationAutomaticTransactionGeneratorConfiguration) {
+          configurationData = merge<ChargingStationConfiguration>(
+            configurationData,
+            chargingStationAutomaticTransactionGeneratorConfiguration
+          );
+        }
+        if (this.connectors.size > 0) {
+          configurationData.connectorsStatus = [...this.connectors.values()].map(
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
+            ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest
+          );
+        }
+        if (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;
+          });
+        }
         delete configurationData.configurationHash;
         const configurationHash = crypto
           .createHash(Constants.DEFAULT_HASH_ALGORITHM)
           .update(JSON.stringify(configurationData))
           .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 = 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.sharedLRUCache.setChargingStationConfiguration(configurationData);
+              this.configurationFileHash = configurationHash;
+            })
+            .catch((error) => {
+              FileUtils.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 ${
@@ -1359,10 +1623,10 @@ export default class ChargingStation {
         }
       } catch (error) {
         FileUtils.handleFileException(
-          this.logPrefix(),
-          FileType.ChargingStationConfiguration,
           this.configurationFile,
-          error as NodeJS.ErrnoException
+          FileType.ChargingStationConfiguration,
+          error as NodeJS.ErrnoException,
+          this.logPrefix()
         );
       }
     } else {
@@ -1372,22 +1636,19 @@ export default class ChargingStation {
     }
   }
 
-  private getOcppConfigurationFromTemplate(): ChargingStationOcppConfiguration | null {
-    return this.getTemplateFromFile()?.Configuration ?? null;
+  private getOcppConfigurationFromTemplate(): ChargingStationOcppConfiguration | undefined {
+    return this.getTemplateFromFile()?.Configuration;
   }
 
-  private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | null {
-    let configuration: ChargingStationConfiguration = null;
+  private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined {
     if (this.getOcppPersistentConfiguration() === true) {
-      const configurationFromFile = this.getConfigurationFromFile();
-      configuration = configurationFromFile?.configurationKey && configurationFromFile;
+      return { configurationKey: this.getConfigurationFromFile()?.configurationKey };
     }
-    configuration && delete configuration.stationInfo;
-    return configuration;
   }
 
-  private getOcppConfiguration(): ChargingStationOcppConfiguration | null {
-    let ocppConfiguration: ChargingStationOcppConfiguration = this.getOcppConfigurationFromFile();
+  private getOcppConfiguration(): ChargingStationOcppConfiguration | undefined {
+    let ocppConfiguration: ChargingStationOcppConfiguration | undefined =
+      this.getOcppConfigurationFromFile();
     if (!ocppConfiguration) {
       ocppConfiguration = this.getOcppConfigurationFromTemplate();
     }
@@ -1412,7 +1673,7 @@ export default class ChargingStation {
           if (this.isRegistered() === false) {
             this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
             await Utils.sleep(
-              this.bootNotificationResponse?.interval
+              this?.bootNotificationResponse?.interval
                 ? this.bootNotificationResponse.interval * 1000
                 : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL
             );
@@ -1424,7 +1685,7 @@ export default class ChargingStation {
         );
       }
       if (this.isRegistered() === true) {
-        if (this.isInAcceptedState() === true) {
+        if (this.inAcceptedState() === true) {
           await this.startMessageSequence();
         }
       } else {
@@ -1434,7 +1695,7 @@ export default class ChargingStation {
       }
       this.wsConnectionRestarted = false;
       this.autoReconnectRetryCount = 0;
-      parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+      parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
     } else {
       logger.warn(
         `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`
@@ -1464,155 +1725,137 @@ export default class ChargingStation {
         this.started === true && (await this.reconnect());
         break;
     }
-    parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+  }
+
+  private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined {
+    const cachedRequest = this.requests.get(messageId);
+    if (Array.isArray(cachedRequest) === true) {
+      return cachedRequest;
+    }
+    throw new OCPPError(
+      ErrorType.PROTOCOL_ERROR,
+      `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString(
+        messageType
+      )} is not an array`,
+      undefined,
+      cachedRequest as JsonType
+    );
+  }
+
+  private async handleIncomingMessage(request: IncomingRequest): Promise<void> {
+    const [messageType, messageId, commandName, commandPayload] = request;
+    if (this.getEnableStatistics() === true) {
+      this.performanceStatistics?.addRequestStatistic(commandName, messageType);
+    }
+    logger.debug(
+      `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
+        request
+      )}`
+    );
+    // Process the message
+    await this.ocppIncomingRequestService.incomingRequestHandler(
+      this,
+      messageId,
+      commandName,
+      commandPayload
+    );
+  }
+
+  private handleResponseMessage(response: Response): void {
+    const [messageType, messageId, commandPayload] = response;
+    if (this.requests.has(messageId) === false) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Response for unknown message id ${messageId}`,
+        undefined,
+        commandPayload
+      );
+    }
+    // Respond
+    const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest(
+      messageType,
+      messageId
+    );
+    logger.debug(
+      `${this.logPrefix()} << Command '${
+        requestCommandName ?? Constants.UNKNOWN_COMMAND
+      }' received response payload: ${JSON.stringify(response)}`
+    );
+    responseCallback(commandPayload, requestPayload);
+  }
+
+  private handleErrorMessage(errorResponse: ErrorResponse): void {
+    const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse;
+    if (this.requests.has(messageId) === false) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Error response for unknown message id ${messageId}`,
+        undefined,
+        { errorType, errorMessage, errorDetails }
+      );
+    }
+    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId);
+    logger.debug(
+      `${this.logPrefix()} << Command '${
+        requestCommandName ?? Constants.UNKNOWN_COMMAND
+      }' 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 messageId: string;
-    let commandName: IncomingRequestCommand;
-    let commandPayload: JsonType;
-    let errorType: ErrorType;
-    let errorMessage: string;
-    let errorDetails: JsonType;
-    let responseCallback: ResponseCallback;
-    let errorCallback: ErrorCallback;
-    let requestCommandName: RequestCommand | IncomingRequestCommand;
-    let requestPayload: JsonType;
-    let cachedRequest: CachedRequest;
-    let errMsg: string;
+    let errorMsg: string;
     try {
-      const request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
+      request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
       if (Array.isArray(request) === true) {
-        [messageType, messageId] = request;
+        [messageType] = request;
         // Check the type of message
         switch (messageType) {
           // Incoming Message
           case MessageType.CALL_MESSAGE:
-            [, , commandName, commandPayload] = request as IncomingRequest;
-            if (this.getEnableStatistics() === true) {
-              this.performanceStatistics.addRequestStatistic(commandName, messageType);
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
-                request
-              )}`
-            );
-            // Process the message
-            await this.ocppIncomingRequestService.incomingRequestHandler(
-              this,
-              messageId,
-              commandName,
-              commandPayload
-            );
+            await this.handleIncomingMessage(request as IncomingRequest);
             break;
-          // Outcome Message
+          // Response Message
           case MessageType.CALL_RESULT_MESSAGE:
-            [, , commandPayload] = request as Response;
-            if (this.requests.has(messageId) === false) {
-              // Error
-              throw new OCPPError(
-                ErrorType.INTERNAL_ERROR,
-                `Response for unknown message id ${messageId}`,
-                null,
-                commandPayload
-              );
-            }
-            // Respond
-            cachedRequest = this.requests.get(messageId);
-            if (Array.isArray(cachedRequest) === true) {
-              [responseCallback, errorCallback, requestCommandName, requestPayload] = cachedRequest;
-            } else {
-              throw new OCPPError(
-                ErrorType.PROTOCOL_ERROR,
-                `Cached request for message id ${messageId} response is not an array`,
-                null,
-                cachedRequest as unknown as JsonType
-              );
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${
-                requestCommandName ?? Constants.UNKNOWN_COMMAND
-              }' received response payload: ${JSON.stringify(request)}`
-            );
-            responseCallback(commandPayload, requestPayload);
+            this.handleResponseMessage(request as Response);
             break;
           // Error Message
           case MessageType.CALL_ERROR_MESSAGE:
-            [, , errorType, errorMessage, errorDetails] = request as ErrorResponse;
-            if (this.requests.has(messageId) === false) {
-              // Error
-              throw new OCPPError(
-                ErrorType.INTERNAL_ERROR,
-                `Error response for unknown message id ${messageId}`,
-                null,
-                { errorType, errorMessage, errorDetails }
-              );
-            }
-            cachedRequest = this.requests.get(messageId);
-            if (Array.isArray(cachedRequest) === true) {
-              [, errorCallback, requestCommandName] = cachedRequest;
-            } else {
-              throw new OCPPError(
-                ErrorType.PROTOCOL_ERROR,
-                `Cached request for message id ${messageId} error response is not an array`,
-                null,
-                cachedRequest as unknown as JsonType
-              );
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${
-                requestCommandName ?? Constants.UNKNOWN_COMMAND
-              }' received error payload: ${JSON.stringify(request)}`
-            );
-            errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails));
+            this.handleErrorMessage(request as ErrorResponse);
             break;
-          // Error
+          // 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(MessageChannelUtils.buildUpdatedMessage(this));
       } else {
         throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming message is not an array', null, {
           request,
         });
       }
     } catch (error) {
-      // Log
-      logger.error(
-        `${this.logPrefix()} Incoming OCPP command '${
-          commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
-        }' message '${data.toString()}'${
-          messageType !== MessageType.CALL_MESSAGE
-            ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
-            : ''
-        } processing error:`,
-        error
-      );
-      if (error instanceof OCPPError === false) {
-        logger.warn(
-          `${this.logPrefix()} Error thrown at incoming OCPP command '${
-            commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
-          }' message '${data.toString()}' handling is not an OCPPError:`,
-          error
-        );
-      }
+      let commandName: IncomingRequestCommand;
+      let requestCommandName: RequestCommand | IncomingRequestCommand;
+      let errorCallback: ErrorCallback;
+      const [, messageId] = request;
       switch (messageType) {
         case MessageType.CALL_MESSAGE:
+          [, , commandName] = request as IncomingRequest;
           // Send error
-          await this.ocppRequestService.sendError(
-            this,
-            messageId,
-            error as OCPPError,
-            commandName ?? requestCommandName ?? null
-          );
+          await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName);
           break;
         case MessageType.CALL_RESULT_MESSAGE:
         case MessageType.CALL_ERROR_MESSAGE:
-          if (errorCallback) {
+          if (this.requests.has(messageId) === true) {
+            [, 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 {
@@ -1621,61 +1864,73 @@ export default class ChargingStation {
           }
           break;
       }
+      if (error instanceof OCPPError === false) {
+        logger.warn(
+          `${this.logPrefix()} Error thrown at incoming OCPP command '${
+            commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+          }' message '${data.toString()}' handling is not an OCPPError:`,
+          error
+        );
+      }
+      logger.error(
+        `${this.logPrefix()} Incoming OCPP command '${
+          commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+        }' message '${data.toString()}'${
+          messageType !== MessageType.CALL_MESSAGE
+            ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
+            : ''
+        } processing error:`,
+        error
+      );
     }
   }
 
   private onPing(): void {
-    logger.debug(this.logPrefix() + ' Received a WS ping (rfc6455) from the server');
+    logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`);
   }
 
   private onPong(): void {
-    logger.debug(this.logPrefix() + ' Received a WS pong (rfc6455) from the server');
+    logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`);
   }
 
   private onError(error: WSError): void {
     this.closeWSConnection();
-    logger.error(this.logPrefix() + ' WebSocket error:', error);
+    logger.error(`${this.logPrefix()} WebSocket error:`, error);
   }
 
-  private getEnergyActiveImportRegister(
-    connectorStatus: ConnectorStatus,
-    meterStop = false
-  ): number {
+  private getEnergyActiveImportRegister(connectorStatus: ConnectorStatus, rounded = false): number {
     if (this.getMeteringPerTransaction() === true) {
       return (
-        (meterStop === true
+        (rounded === true
           ? Math.round(connectorStatus?.transactionEnergyActiveImportRegisterValue)
           : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0
       );
     }
     return (
-      (meterStop === true
+      (rounded === true
         ? Math.round(connectorStatus?.energyActiveImportRegisterValue)
         : connectorStatus?.energyActiveImportRegisterValue) ?? 0
     );
   }
 
-  private getUseConnectorId0(stationInfo?: ChargingStationInfo): boolean {
-    const localStationInfo = stationInfo ?? this.stationInfo;
-    return !Utils.isUndefined(localStationInfo.useConnectorId0)
-      ? localStationInfo.useConnectorId0
-      : true;
-  }
-
-  private getNumberOfRunningTransactions(): number {
-    let trxCount = 0;
-    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> {
-    for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-        await this.stopTransactionOnConnector(connectorId, reason);
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (connectorStatus.transactionStarted === true) {
+            await this.stopTransactionOnConnector(connectorId, reason);
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+          await this.stopTransactionOnConnector(connectorId, reason);
+        }
       }
     }
   }
@@ -1701,44 +1956,32 @@ export default class ChargingStation {
   }
 
   // -1 for unlimited, 0 for disabling
-  private getAutoReconnectMaxRetries(): number {
-    if (!Utils.isUndefined(this.stationInfo.autoReconnectMaxRetries)) {
-      return this.stationInfo.autoReconnectMaxRetries;
-    }
-    if (!Utils.isUndefined(Configuration.getAutoReconnectMaxRetries())) {
-      return Configuration.getAutoReconnectMaxRetries();
-    }
-    return -1;
+  private getAutoReconnectMaxRetries(): number | undefined {
+    return (
+      this.stationInfo.autoReconnectMaxRetries ?? Configuration.getAutoReconnectMaxRetries() ?? -1
+    );
   }
 
   // 0 for disabling
-  private getRegistrationMaxRetries(): number {
-    if (!Utils.isUndefined(this.stationInfo.registrationMaxRetries)) {
-      return this.stationInfo.registrationMaxRetries;
-    }
-    return -1;
+  private getRegistrationMaxRetries(): number | undefined {
+    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();
     }
     return powerDivider;
   }
 
-  private getMaximumPower(stationInfo?: ChargingStationInfo): number {
-    const localStationInfo = stationInfo ?? this.stationInfo;
-    return (localStationInfo['maxPower'] as number) ?? localStationInfo.maximumPower;
-  }
-
   private getMaximumAmperage(stationInfo: ChargingStationInfo): number | undefined {
     const maximumPower = this.getMaximumPower(stationInfo);
     switch (this.getCurrentOutType(stationInfo)) {
       case CurrentType.AC:
         return ACElectricUtils.amperagePerPhaseFromPower(
           this.getNumberOfPhases(stationInfo),
-          maximumPower / this.getNumberOfConnectors(),
+          maximumPower / (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
           this.getVoltageOut(stationInfo)
         );
       case CurrentType.DC:
@@ -1748,7 +1991,7 @@ export default class ChargingStation {
 
   private getAmperageLimitation(): number | undefined {
     if (
-      this.stationInfo.amperageLimitationOcppKey &&
+      Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
       ChargingStationConfigurationUtils.getConfigurationKey(
         this,
         this.stationInfo.amperageLimitationOcppKey
@@ -1759,63 +2002,12 @@ export default class ChargingStation {
           ChargingStationConfigurationUtils.getConfigurationKey(
             this,
             this.stationInfo.amperageLimitationOcppKey
-          ).value
+          )?.value
         ) / ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
       );
     }
   }
 
-  private getChargingProfilePowerLimit(connectorId: number): number | undefined {
-    let limit: number, matchingChargingProfile: ChargingProfile;
-    let chargingProfiles: ChargingProfile[] = [];
-    // Get charging profiles for connector and sort by stack level
-    chargingProfiles = this.getConnectorStatus(connectorId).chargingProfiles.sort(
-      (a, b) => b.stackLevel - a.stackLevel
-    );
-    // Get profiles on connector 0
-    if (this.getConnectorStatus(0).chargingProfiles) {
-      chargingProfiles.push(
-        ...this.getConnectorStatus(0).chargingProfiles.sort((a, b) => b.stackLevel - a.stackLevel)
-      );
-    }
-    if (!Utils.isEmptyArray(chargingProfiles)) {
-      const result = ChargingStationUtils.getLimitFromChargingProfiles(
-        chargingProfiles,
-        this.logPrefix()
-      );
-      if (!Utils.isNullOrUndefined(result)) {
-        limit = result.limit;
-        matchingChargingProfile = result.matchingChargingProfile;
-        switch (this.getCurrentOutType()) {
-          case CurrentType.AC:
-            limit =
-              matchingChargingProfile.chargingSchedule.chargingRateUnit ===
-              ChargingRateUnitType.WATT
-                ? limit
-                : ACElectricUtils.powerTotal(this.getNumberOfPhases(), this.getVoltageOut(), limit);
-            break;
-          case CurrentType.DC:
-            limit =
-              matchingChargingProfile.chargingSchedule.chargingRateUnit ===
-              ChargingRateUnitType.WATT
-                ? limit
-                : DCElectricUtils.power(this.getVoltageOut(), limit);
-        }
-        const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
-        if (limit > connectorMaximumPower) {
-          logger.error(
-            `${this.logPrefix()} Charging profile id ${
-              matchingChargingProfile.chargingProfileId
-            } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}, dump charging profiles' stack: %j`,
-            this.getConnectorStatus(connectorId).chargingProfiles
-          );
-          limit = connectorMaximumPower;
-        }
-      }
-    }
-    return limit;
-  }
-
   private async startMessageSequence(): Promise<void> {
     if (this.stationInfo?.autoRegister === true) {
       await this.ocppRequestService.requestHandler<
@@ -1830,38 +2022,35 @@ export default class ChargingStation {
     // Start heartbeat
     this.startHeartbeat();
     // Initialize connectors status
-    for (const connectorId of this.connectors.keys()) {
-      let connectorStatus: ConnectorStatusEnum;
-      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 = ChargingStationUtils.getBootConnectorStatus(
+              this,
+              connectorId,
+              connectorStatus
+            );
+            await OCPPServiceUtils.sendAndSetConnectorStatus(
+              this,
+              connectorId,
+              connectorBootStatus,
+              evseId
+            );
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0) {
+          const connectorBootStatus = ChargingStationUtils.getBootConnectorStatus(
+            this,
+            connectorId,
+            this.getConnectorStatus(connectorId)
+          );
+          await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorBootStatus);
+        }
       }
-      await this.ocppRequestService.requestHandler<
-        StatusNotificationRequest,
-        StatusNotificationResponse
-      >(
-        this,
-        RequestCommand.STATUS_NOTIFICATION,
-        OCPPServiceUtils.buildStatusNotificationRequest(this, connectorId, connectorStatus)
-      );
-      this.getConnectorStatus(connectorId).status = connectorStatus;
     }
     if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
       await this.ocppRequestService.requestHandler<
@@ -1874,7 +2063,7 @@ export default class ChargingStation {
     }
 
     // Start the ATG
-    if (this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true) {
+    if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
       this.startAutomaticTransactionGenerator();
     }
     this.wsConnectionRestarted === true && this.flushMessageBuffer();
@@ -1893,21 +2082,44 @@ export default 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
-          )
-        );
-        this.getConnectorStatus(connectorId).status = null;
+            RequestCommand.STATUS_NOTIFICATION,
+            OCPPServiceUtils.buildStatusNotificationRequest(
+              this,
+              connectorId,
+              ConnectorStatusEnum.Unavailable
+            )
+          );
+          delete this.getConnectorStatus(connectorId)?.status;
+        }
       }
     }
   }
@@ -1921,33 +2133,29 @@ export default class ChargingStation {
           ChargingStationConfigurationUtils.getConfigurationKey(
             this,
             StandardParametersKey.WebSocketPingInterval
-          ).value
+          )?.value
         )
       : 0;
     if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
       this.webSocketPingSetInterval = setInterval(() => {
         if (this.isWebSocketConnectionOpened() === true) {
-          this.wsConnection.ping();
+          this.wsConnection?.ping();
         }
       }, webSocketPingInterval * 1000);
       logger.info(
-        this.logPrefix() +
-          ' WebSocket ping started every ' +
-          Utils.formatDurationSeconds(webSocketPingInterval)
+        `${this.logPrefix()} WebSocket ping started every ${Utils.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 ${Utils.formatDurationSeconds(
+          webSocketPingInterval
+        )}`
       );
     } else {
       logger.error(
-        `${this.logPrefix()} WebSocket ping interval set to ${
-          webSocketPingInterval
-            ? Utils.formatDurationSeconds(webSocketPingInterval)
-            : webSocketPingInterval
-        }, not starting the WebSocket ping`
+        `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval}, not starting the WebSocket ping`
       );
     }
   }
@@ -1955,86 +2163,53 @@ export default class ChargingStation {
   private stopWebSocketPing(): void {
     if (this.webSocketPingSetInterval) {
       clearInterval(this.webSocketPingSetInterval);
+      delete this.webSocketPingSetInterval;
     }
   }
 
   private getConfiguredSupervisionUrl(): URL {
-    const supervisionUrls = this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls();
-    if (!Utils.isEmptyArray(supervisionUrls)) {
+    const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls();
+    if (Utils.isNotEmptyArray(supervisionUrls)) {
+      let configuredSupervisionUrlIndex: number;
       switch (Configuration.getSupervisionUrlDistribution()) {
-        case SupervisionUrlDistribution.ROUND_ROBIN:
-          // FIXME
-          this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
-          break;
         case SupervisionUrlDistribution.RANDOM:
-          this.configuredSupervisionUrlIndex = Math.floor(
-            Utils.secureRandom() * supervisionUrls.length
-          );
+          configuredSupervisionUrlIndex = Math.floor(Utils.secureRandom() * supervisionUrls.length);
           break;
+        case SupervisionUrlDistribution.ROUND_ROBIN:
         case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
-          this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
-          break;
         default:
-          logger.error(
-            `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${
-              SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
-            }`
-          );
-          this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
+          Object.values(SupervisionUrlDistribution).includes(
+            Configuration.getSupervisionUrlDistribution()
+          ) === false &&
+            logger.error(
+              `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${
+                SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
+              }`
+            );
+          configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
           break;
       }
-      return new URL(supervisionUrls[this.configuredSupervisionUrlIndex]);
+      return new URL(supervisionUrls[configuredSupervisionUrlIndex]);
     }
     return new URL(supervisionUrls as string);
   }
 
-  private getHeartbeatInterval(): number {
-    const HeartbeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
-      this,
-      StandardParametersKey.HeartbeatInterval
-    );
-    if (HeartbeatInterval) {
-      return Utils.convertToInt(HeartbeatInterval.value) * 1000;
-    }
-    const HeartBeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
-      this,
-      StandardParametersKey.HeartBeatInterval
-    );
-    if (HeartBeatInterval) {
-      return Utils.convertToInt(HeartBeatInterval.value) * 1000;
-    }
-    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;
-  }
-
   private stopHeartbeat(): void {
     if (this.heartbeatSetInterval) {
       clearInterval(this.heartbeatSetInterval);
+      delete this.heartbeatSetInterval;
     }
   }
 
   private terminateWSConnection(): void {
     if (this.isWebSocketConnectionOpened() === true) {
-      this.wsConnection.terminate();
+      this.wsConnection?.terminate();
       this.wsConnection = null;
     }
   }
 
-  private stopMeterValues(connectorId: number) {
-    if (this.getConnectorStatus(connectorId)?.transactionSetInterval) {
-      clearInterval(this.getConnectorStatus(connectorId).transactionSetInterval);
-    }
-  }
-
   private getReconnectExponentialDelay(): boolean {
-    return !Utils.isUndefined(this.stationInfo.reconnectExponentialDelay)
-      ? this.stationInfo.reconnectExponentialDelay
-      : false;
+    return this.stationInfo?.reconnectExponentialDelay ?? false;
   }
 
   private async reconnect(): Promise<void> {
@@ -2043,7 +2218,7 @@ export default 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 (
@@ -2067,10 +2242,13 @@ export default class ChargingStation {
       );
       await Utils.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 },
+        {
+          ...(this.stationInfo?.wsOptions ?? {}),
+          handshakeTimeout: reconnectTimeout,
+        },
         { closeOpened: true }
       );
       this.wsConnectionRestarted = true;
@@ -2082,17 +2260,4 @@ export default class ChargingStation {
       );
     }
   }
-
-  private getAutomaticTransactionGeneratorConfigurationFromTemplate(): AutomaticTransactionGeneratorConfiguration | null {
-    return this.getTemplateFromFile()?.AutomaticTransactionGenerator ?? null;
-  }
-
-  private initializeConnectorStatus(connectorId: number): void {
-    this.getConnectorStatus(connectorId).idTagLocalAuthorized = false;
-    this.getConnectorStatus(connectorId).idTagAuthorized = false;
-    this.getConnectorStatus(connectorId).transactionRemoteStarted = false;
-    this.getConnectorStatus(connectorId).transactionStarted = false;
-    this.getConnectorStatus(connectorId).energyActiveImportRegisterValue = 0;
-    this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;
-  }
 }