build(deps): apply updates
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 441c5d1e95652a8d7cfc67174cf1b2834848cea5..5bc0ec9ad24d8ae76f486f388eb86d03a2cad0b8 100644 (file)
@@ -1,6 +1,7 @@
 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
 import { createHash } from 'node:crypto';
+import { EventEmitter } from 'node:events';
 import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
 import { dirname, join } from 'node:path';
 import { URL } from 'node:url';
@@ -21,6 +22,7 @@ import {
 import {
   buildConnectorsMap,
   checkChargingStation,
+  checkConfiguration,
   checkConnectorsConfiguration,
   checkStationInfoConnectorStatus,
   checkTemplate,
@@ -36,11 +38,9 @@ import {
   getMaxNumberOfEvses,
   getNumberOfReservableConnectors,
   getPhaseRotationValue,
-  hasFeatureProfile,
   hasReservationExpired,
   initializeConnectorsMapStatus,
   propagateSerialNumber,
-  removeExpiredReservations,
   stationTemplateToStationInfo,
   warnTemplateKeysDeprecation,
 } from './Helpers';
@@ -49,13 +49,16 @@ import {
   OCPP16IncomingRequestService,
   OCPP16RequestService,
   OCPP16ResponseService,
-  OCPP16ServiceUtils,
   OCPP20IncomingRequestService,
   OCPP20RequestService,
   OCPP20ResponseService,
   type OCPPIncomingRequestService,
   type OCPPRequestService,
-  OCPPServiceUtils,
+  buildMeterValue,
+  buildStatusNotificationRequest,
+  buildTransactionEndMeterValue,
+  getMessageTypeString,
+  sendAndSetConnectorStatus,
 } from './ocpp';
 import { SharedLRUCache } from './SharedLRUCache';
 import { BaseError, OCPPError } from '../exception';
@@ -67,6 +70,7 @@ import {
   type BootNotificationResponse,
   type CachedRequest,
   type ChargingStationConfiguration,
+  ChargingStationEvents,
   type ChargingStationInfo,
   type ChargingStationOcppConfiguration,
   type ChargingStationTemplate,
@@ -87,9 +91,7 @@ import {
   type HeartbeatResponse,
   type IncomingRequest,
   type IncomingRequestCommand,
-  type JsonType,
   MessageType,
-  type MeterValue,
   MeterValueMeasurand,
   type MeterValuesRequest,
   type MeterValuesResponse,
@@ -111,7 +113,7 @@ import {
   type StopTransactionResponse,
   SupervisionUrlDistribution,
   SupportedFeatureProfiles,
-  VendorParametersKey,
+  Voltage,
   type WSError,
   WebSocketCloseEventStatusCode,
   type WsOptions,
@@ -149,10 +151,10 @@ import {
   roundTo,
   secureRandom,
   sleep,
-  // watchJsonFile,
+  watchJsonFile,
 } from '../utils';
 
-export class ChargingStation {
+export class ChargingStation extends EventEmitter {
   public readonly index: number;
   public readonly templateFile: string;
   public stationInfo!: ChargingStationInfo;
@@ -180,21 +182,20 @@ export class ChargingStation {
   private ocppIncomingRequestService!: OCPPIncomingRequestService;
   private readonly messageBuffer: Set<string>;
   private configuredSupervisionUrl!: URL;
-  private wsConnectionRestarted: boolean;
   private autoReconnectRetryCount: number;
   private templateFileWatcher!: FSWatcher | undefined;
   private templateFileHash!: string;
   private readonly sharedLRUCache: SharedLRUCache;
   private webSocketPingSetInterval?: NodeJS.Timeout;
   private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
-  private reservationExpirationSetInterval?: NodeJS.Timeout;
+  private flushMessageBufferSetInterval?: NodeJS.Timeout;
 
   constructor(index: number, templateFile: string) {
+    super();
     this.started = false;
     this.starting = false;
     this.stopping = false;
     this.wsConnection = null;
-    this.wsConnectionRestarted = false;
     this.autoReconnectRetryCount = 0;
     this.index = index;
     this.templateFile = templateFile;
@@ -206,6 +207,16 @@ export class ChargingStation {
     this.idTagsCache = IdTagsCache.getInstance();
     this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
 
+    this.on(ChargingStationEvents.started, () => {
+      parentPort?.postMessage(buildStartedMessage(this));
+    });
+    this.on(ChargingStationEvents.stopped, () => {
+      parentPort?.postMessage(buildStoppedMessage(this));
+    });
+    this.on(ChargingStationEvents.updated, () => {
+      parentPort?.postMessage(buildUpdatedMessage(this));
+    });
+
     this.initialize();
   }
 
@@ -216,43 +227,39 @@ export class ChargingStation {
   private get wsConnectionUrl(): URL {
     return new URL(
       `${
-        this.getSupervisionUrlOcppConfiguration() &&
-        isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
-        isNotEmptyString(getConfigurationKey(this, this.getSupervisionUrlOcppKey())?.value)
-          ? getConfigurationKey(this, this.getSupervisionUrlOcppKey())!.value
+        this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+        isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
+        isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)?.value)
+          ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)!.value
           : this.configuredSupervisionUrl.href
       }/${this.stationInfo.chargingStationId}`,
     );
   }
 
   public logPrefix = (): string => {
-    return logPrefix(
-      ` ${
-        (isNotEmptyString(this?.stationInfo?.chargingStationId)
-          ? this?.stationInfo?.chargingStationId
-          : getChargingStationId(this.index, this.getTemplateFromFile()!)) ??
-        'Error at building log prefix'
-      } |`,
-    );
+    if (isNotEmptyString(this?.stationInfo?.chargingStationId)) {
+      return logPrefix(` ${this?.stationInfo?.chargingStationId} |`);
+    }
+    let stationTemplate: ChargingStationTemplate | undefined;
+    try {
+      stationTemplate = JSON.parse(
+        readFileSync(this.templateFile, 'utf8'),
+      ) as ChargingStationTemplate;
+    } catch {
+      stationTemplate = undefined;
+    }
+    return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`);
   };
 
   public hasIdTags(): boolean {
     return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo)!));
   }
 
-  public getEnableStatistics(): boolean {
-    return this.stationInfo.enableStatistics ?? false;
-  }
-
-  public getRemoteAuthorization(): boolean {
-    return this.stationInfo.remoteAuthorization ?? true;
-  }
-
   public getNumberOfPhases(stationInfo?: ChargingStationInfo): number {
     const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
     switch (this.getCurrentOutType(stationInfo)) {
       case CurrentType.AC:
-        return !isUndefined(localStationInfo.numberOfPhases) ? localStationInfo.numberOfPhases! : 3;
+        return localStationInfo.numberOfPhases ?? 3;
       case CurrentType.DC:
         return 0;
     }
@@ -262,10 +269,6 @@ export class ChargingStation {
     return this?.wsConnection?.readyState === WebSocket.OPEN;
   }
 
-  public getRegistrationStatus(): RegistrationStatusEnumType | undefined {
-    return this?.bootNotificationResponse?.status;
-  }
-
   public inUnknownState(): boolean {
     return isNullOrUndefined(this?.bootNotificationResponse?.status);
   }
@@ -341,27 +344,6 @@ export class ChargingStation {
     return this.connectors.get(connectorId);
   }
 
-  public getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType {
-    return (stationInfo ?? this.stationInfo)?.currentOutType ?? CurrentType.AC;
-  }
-
-  public getOcppStrictCompliance(): boolean {
-    return this.stationInfo?.ocppStrictCompliance ?? true;
-  }
-
-  public getVoltageOut(stationInfo?: ChargingStationInfo): number {
-    const defaultVoltageOut = getDefaultVoltageOut(
-      this.getCurrentOutType(stationInfo),
-      this.logPrefix(),
-      this.templateFile,
-    );
-    return (stationInfo ?? this.stationInfo).voltageOut ?? defaultVoltageOut;
-  }
-
-  public getMaximumPower(stationInfo?: ChargingStationInfo): number {
-    return (stationInfo ?? this.stationInfo).maximumPower!;
-  }
-
   public getConnectorMaximumAvailablePower(connectorId: number): number {
     let connectorAmperageLimitationPowerLimit: number | undefined;
     if (
@@ -369,17 +351,17 @@ export class ChargingStation {
       this.getAmperageLimitation()! < this.stationInfo.maximumAmperage!
     ) {
       connectorAmperageLimitationPowerLimit =
-        (this.getCurrentOutType() === CurrentType.AC
+        (this.stationInfo?.currentOutType === CurrentType.AC
           ? ACElectricUtils.powerTotal(
               this.getNumberOfPhases(),
-              this.getVoltageOut(),
+              this.stationInfo.voltageOut!,
               this.getAmperageLimitation()! *
                 (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
             )
-          : DCElectricUtils.power(this.getVoltageOut(), this.getAmperageLimitation()!)) /
+          : DCElectricUtils.power(this.stationInfo.voltageOut!, this.getAmperageLimitation()!)) /
         this.powerDivider;
     }
-    const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
+    const connectorMaximumPower = this.stationInfo.maximumPower! / this.powerDivider;
     const connectorChargingProfilesPowerLimit =
       getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId);
     return min(
@@ -432,34 +414,6 @@ export class ChargingStation {
     return numberOfRunningTransactions;
   }
 
-  public getOutOfOrderEndMeterValues(): boolean {
-    return this.stationInfo?.outOfOrderEndMeterValues ?? false;
-  }
-
-  public getBeginEndMeterValues(): boolean {
-    return this.stationInfo?.beginEndMeterValues ?? false;
-  }
-
-  public getMeteringPerTransaction(): boolean {
-    return this.stationInfo?.meteringPerTransaction ?? true;
-  }
-
-  public getTransactionDataMeterValues(): boolean {
-    return this.stationInfo?.transactionDataMeterValues ?? false;
-  }
-
-  public getMainVoltageMeterValues(): boolean {
-    return this.stationInfo?.mainVoltageMeterValues ?? true;
-  }
-
-  public getPhaseLineToLineVoltageMeterValues(): boolean {
-    return this.stationInfo?.phaseLineToLineVoltageMeterValues ?? false;
-  }
-
-  public getCustomValueLimitationMeterValues(): boolean {
-    return this.stationInfo?.customValueLimitationMeterValues ?? true;
-  }
-
   public getConnectorIdByTransactionId(transactionId: number): number | undefined {
     if (this.hasEvses) {
       for (const evseStatus of this.evses.values()) {
@@ -497,7 +451,9 @@ export class ChargingStation {
       this,
       StandardParametersKey.AuthorizeRemoteTxRequests,
     );
-    return authorizeRemoteTxRequests ? convertToBoolean(authorizeRemoteTxRequests.value) : false;
+    return authorizeRemoteTxRequests !== undefined
+      ? convertToBoolean(authorizeRemoteTxRequests.value)
+      : false;
   }
 
   public getLocalAuthListEnabled(): boolean {
@@ -505,16 +461,18 @@ export class ChargingStation {
       this,
       StandardParametersKey.LocalAuthListEnabled,
     );
-    return localAuthListEnabled ? convertToBoolean(localAuthListEnabled.value) : false;
+    return localAuthListEnabled !== undefined
+      ? convertToBoolean(localAuthListEnabled.value)
+      : false;
   }
 
   public getHeartbeatInterval(): number {
     const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval);
-    if (HeartbeatInterval) {
+    if (HeartbeatInterval !== undefined) {
       return secondsToMilliseconds(convertToInt(HeartbeatInterval.value));
     }
     const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval);
-    if (HeartBeatInterval) {
+    if (HeartBeatInterval !== undefined) {
       return secondsToMilliseconds(convertToInt(HeartBeatInterval.value));
     }
     this.stationInfo?.autoRegister === false &&
@@ -528,10 +486,10 @@ export class ChargingStation {
 
   public setSupervisionUrl(url: string): void {
     if (
-      this.getSupervisionUrlOcppConfiguration() &&
-      isNotEmptyString(this.getSupervisionUrlOcppKey())
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+      isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey)
     ) {
-      setConfigurationKeyValue(this, this.getSupervisionUrlOcppKey(), url);
+      setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey!, url);
     } else {
       this.stationInfo.supervisionUrls = url;
       this.saveStationInfo();
@@ -540,7 +498,7 @@ export class ChargingStation {
   }
 
   public startHeartbeat(): void {
-    if (this.getHeartbeatInterval() > 0 && !this.heartbeatSetInterval) {
+    if (this.getHeartbeatInterval() > 0 && this.heartbeatSetInterval === undefined) {
       this.heartbeatSetInterval = setInterval(() => {
         this.ocppRequestService
           .requestHandler<HeartbeatRequest, HeartbeatResponse>(this, RequestCommand.HEARTBEAT)
@@ -556,7 +514,7 @@ export class ChargingStation {
           this.getHeartbeatInterval(),
         )}`,
       );
-    } else if (this.heartbeatSetInterval) {
+    } else if (this.heartbeatSetInterval !== undefined) {
       logger.info(
         `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds(
           this.getHeartbeatInterval(),
@@ -613,8 +571,7 @@ export class ChargingStation {
     }
     if (interval > 0) {
       this.getConnectorStatus(connectorId)!.transactionSetInterval = setInterval(() => {
-        // FIXME: Implement OCPP version agnostic helpers
-        const meterValue: MeterValue = OCPP16ServiceUtils.buildMeterValue(
+        const meterValue = buildMeterValue(
           this,
           connectorId,
           this.getConnectorStatus(connectorId)!.transactionId!,
@@ -647,7 +604,7 @@ export class ChargingStation {
   }
 
   public stopMeterValues(connectorId: number) {
-    if (this.getConnectorStatus(connectorId)?.transactionSetInterval) {
+    if (this.getConnectorStatus(connectorId)?.transactionSetInterval !== undefined) {
       clearInterval(this.getConnectorStatus(connectorId)?.transactionSetInterval);
     }
   }
@@ -656,65 +613,51 @@ export class ChargingStation {
     if (this.started === false) {
       if (this.starting === false) {
         this.starting = true;
-        if (this.getEnableStatistics() === true) {
+        if (this.stationInfo?.enableStatistics === true) {
           this.performanceStatistics?.start();
         }
-        if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) {
-          this.startReservationExpirationSetInterval();
-        }
         this.openWSConnection();
         // Monitor charging station template file
-        // FIXME: Disabled until the spurious configuration file change detection is identified
-        // this.templateFileWatcher = watchJsonFile(
-        //   this.templateFile,
-        //   FileType.ChargingStationTemplate,
-        //   this.logPrefix(),
-        //   undefined,
-        //   (event, filename): void => {
-        //     if (isNotEmptyString(filename) && event === 'change') {
-        //       try {
-        //         logger.debug(
-        //           `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
-        //             this.templateFile
-        //           } file have changed, reload`,
-        //         );
-        //         this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
-        //         // Initialize
-        //         this.initialize();
-        //         this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo)!);
-        //         // Restart the ATG
-        //         this.stopAutomaticTransactionGenerator()
-        //           .then(() => {
-        //             delete this.automaticTransactionGeneratorConfiguration;
-        //             if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
-        //               this.startAutomaticTransactionGenerator();
-        //             }
-        //           })
-        //           .catch((err) =>
-        //             logger.error(
-        //               `${this.logPrefix()} failed to stop ATG at ${
-        //                 FileType.ChargingStationTemplate
-        //               } reload`,
-        //               err,
-        //             ),
-        //           );
-        //         if (this.getEnableStatistics() === true) {
-        //           this.performanceStatistics?.restart();
-        //         } else {
-        //           this.performanceStatistics?.stop();
-        //         }
-        //         // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
-        //       } catch (error) {
-        //         logger.error(
-        //           `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
-        //           error,
-        //         );
-        //       }
-        //     }
-        //   },
-        // );
+        this.templateFileWatcher = watchJsonFile(
+          this.templateFile,
+          FileType.ChargingStationTemplate,
+          this.logPrefix(),
+          undefined,
+          (event, filename): void => {
+            if (isNotEmptyString(filename) && event === 'change') {
+              try {
+                logger.debug(
+                  `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
+                    this.templateFile
+                  } file have changed, reload`,
+                );
+                this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
+                // Initialize
+                this.initialize();
+                this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo)!);
+                // Restart the ATG
+                this.stopAutomaticTransactionGenerator();
+                delete this.automaticTransactionGeneratorConfiguration;
+                if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) {
+                  this.startAutomaticTransactionGenerator();
+                }
+                if (this.stationInfo?.enableStatistics === true) {
+                  this.performanceStatistics?.restart();
+                } else {
+                  this.performanceStatistics?.stop();
+                }
+                // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
+              } catch (error) {
+                logger.error(
+                  `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
+                  error,
+                );
+              }
+            }
+          },
+        );
         this.started = true;
-        parentPort?.postMessage(buildStartedMessage(this));
+        this.emit(ChargingStationEvents.started);
         this.starting = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already starting...`);
@@ -730,19 +673,16 @@ export class ChargingStation {
         this.stopping = true;
         await this.stopMessageSequence(reason, stopTransactions);
         this.closeWSConnection();
-        if (this.getEnableStatistics() === true) {
+        if (this.stationInfo?.enableStatistics === true) {
           this.performanceStatistics?.stop();
         }
-        if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) {
-          this.stopReservationExpirationSetInterval();
-        }
         this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
         this.templateFileWatcher?.close();
         this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
         delete this.bootNotificationResponse;
         this.started = false;
         this.saveConfiguration();
-        parentPort?.postMessage(buildStoppedMessage(this));
+        this.emit(ChargingStationEvents.stopped);
         this.stopping = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already stopping...`);
@@ -760,13 +700,14 @@ export class ChargingStation {
   }
 
   public saveOcppConfiguration(): void {
-    if (this.getOcppPersistentConfiguration()) {
+    if (this.stationInfo?.ocppPersistentConfiguration === true) {
       this.saveConfiguration();
     }
   }
 
   public bufferMessage(message: string): void {
     this.messageBuffer.add(message);
+    this.setIntervalFlushMessageBuffer();
   }
 
   public openWSConnection(
@@ -808,7 +749,7 @@ export class ChargingStation {
 
     this.wsConnection = new WebSocket(
       this.wsConnectionUrl,
-      `ocpp${this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16}`,
+      `ocpp${this.stationInfo?.ocppVersion}`,
       options,
     );
 
@@ -847,17 +788,17 @@ export class ChargingStation {
       let automaticTransactionGeneratorConfiguration:
         | AutomaticTransactionGeneratorConfiguration
         | undefined;
-      const automaticTransactionGeneratorConfigurationFromFile =
-        this.getConfigurationFromFile()?.automaticTransactionGenerator;
+      const stationTemplate = this.getTemplateFromFile();
+      const stationConfiguration = this.getConfigurationFromFile();
       if (
-        this.getAutomaticTransactionGeneratorPersistentConfiguration() &&
-        automaticTransactionGeneratorConfigurationFromFile
+        this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true &&
+        stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+        stationConfiguration?.automaticTransactionGenerator
       ) {
         automaticTransactionGeneratorConfiguration =
-          automaticTransactionGeneratorConfigurationFromFile;
+          stationConfiguration?.automaticTransactionGenerator;
       } else {
-        automaticTransactionGeneratorConfiguration =
-          this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+        automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator;
       }
       this.automaticTransactionGeneratorConfiguration = {
         ...Constants.DEFAULT_ATG_CONFIGURATION,
@@ -881,7 +822,7 @@ export class ChargingStation {
       this.automaticTransactionGenerator?.start();
     }
     this.saveAutomaticTransactionGeneratorConfiguration();
-    parentPort?.postMessage(buildUpdatedMessage(this));
+    this.emit(ChargingStationEvents.updated);
   }
 
   public stopAutomaticTransactionGenerator(connectorIds?: number[]): void {
@@ -893,7 +834,7 @@ export class ChargingStation {
       this.automaticTransactionGenerator?.stop();
     }
     this.saveAutomaticTransactionGeneratorConfiguration();
-    parentPort?.postMessage(buildUpdatedMessage(this));
+    this.emit(ChargingStationEvents.updated);
   }
 
   public async stopTransactionOnConnector(
@@ -902,12 +843,11 @@ export class ChargingStation {
   ): Promise<StopTransactionResponse> {
     const transactionId = this.getConnectorStatus(connectorId)?.transactionId;
     if (
-      this.getBeginEndMeterValues() === true &&
-      this.getOcppStrictCompliance() === true &&
-      this.getOutOfOrderEndMeterValues() === false
+      this.stationInfo?.beginEndMeterValues === true &&
+      this.stationInfo?.ocppStrictCompliance === true &&
+      this.stationInfo?.outOfOrderEndMeterValues === false
     ) {
-      // FIXME: Implement OCPP version agnostic helpers
-      const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
+      const transactionEndMeterValue = buildTransactionEndMeterValue(
         this,
         connectorId,
         this.getEnergyActiveImportRegisterByTransactionId(transactionId!),
@@ -941,14 +881,11 @@ export class ChargingStation {
 
   public async addReservation(reservation: Reservation): Promise<void> {
     const reservationFound = this.getReservationBy('reservationId', reservation.reservationId);
-    if (!isUndefined(reservationFound)) {
-      await this.removeReservation(
-        reservationFound!,
-        ReservationTerminationReason.REPLACE_EXISTING,
-      );
+    if (reservationFound !== undefined) {
+      await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING);
     }
     this.getConnectorStatus(reservation.connectorId)!.reservation = reservation;
-    await OCPPServiceUtils.sendAndSetConnectorStatus(
+    await sendAndSetConnectorStatus(
       this,
       reservation.connectorId,
       ConnectorStatusEnum.Reserved,
@@ -970,7 +907,7 @@ export class ChargingStation {
       case ReservationTerminationReason.RESERVATION_CANCELED:
       case ReservationTerminationReason.REPLACE_EXISTING:
       case ReservationTerminationReason.EXPIRED:
-        await OCPPServiceUtils.sendAndSetConnectorStatus(
+        await sendAndSetConnectorStatus(
           this,
           reservation.connectorId,
           ConnectorStatusEnum.Available,
@@ -1030,31 +967,26 @@ export class ChargingStation {
     return false;
   }
 
-  private startReservationExpirationSetInterval(customInterval?: number): void {
-    const interval = customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_INTERVAL;
-    if (interval > 0) {
-      logger.info(
-        `${this.logPrefix()} Reservation expiration date checks started every ${formatDurationMilliSeconds(
-          interval,
-        )}`,
-      );
-      this.reservationExpirationSetInterval = setInterval((): void => {
-        removeExpiredReservations(this).catch(Constants.EMPTY_FUNCTION);
-      }, interval);
+  private setIntervalFlushMessageBuffer(): void {
+    if (this.flushMessageBufferSetInterval === undefined) {
+      this.flushMessageBufferSetInterval = setInterval(() => {
+        if (this.isWebSocketConnectionOpened() === true && this.inAcceptedState() === true) {
+          this.flushMessageBuffer();
+        }
+        if (this.messageBuffer.size === 0) {
+          this.clearIntervalFlushMessageBuffer();
+        }
+      }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL);
     }
   }
 
-  private stopReservationExpirationSetInterval(): void {
-    if (!isNullOrUndefined(this.reservationExpirationSetInterval)) {
-      clearInterval(this.reservationExpirationSetInterval);
+  private clearIntervalFlushMessageBuffer() {
+    if (this.flushMessageBufferSetInterval !== undefined) {
+      clearInterval(this.flushMessageBufferSetInterval);
+      delete this.flushMessageBufferSetInterval;
     }
   }
 
-  // private restartReservationExpiryDateSetInterval(): void {
-  //   this.stopReservationExpirationSetInterval();
-  //   this.startReservationExpirationSetInterval();
-  // }
-
   private getNumberOfReservableConnectors(): number {
     let numberOfReservableConnectors = 0;
     if (this.hasEvses) {
@@ -1089,26 +1021,28 @@ export class ChargingStation {
           [, , 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);
+        this.wsConnection?.send(message, (error?: Error) => {
+          isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!);
+          if (isNullOrUndefined(error)) {
+            logger.debug(
+              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
+                messageType,
+              )} OCPP message sent '${JSON.stringify(message)}'`,
+            );
+            this.messageBuffer.delete(message);
+          } else {
+            logger.debug(
+              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
+                messageType,
+              )} OCPP message '${JSON.stringify(message)}' send failed:`,
+              error,
+            );
+          }
+        });
       }
     }
   }
 
-  private getSupervisionUrlOcppConfiguration(): boolean {
-    return this.stationInfo.supervisionUrlOcppConfiguration ?? false;
-  }
-
-  private getSupervisionUrlOcppKey(): string {
-    return this.stationInfo.supervisionUrlOcppKey ?? VendorParametersKey.ConnectionUrl;
-  }
-
   private getTemplateFromFile(): ChargingStationTemplate | undefined {
     let template: ChargingStationTemplate | undefined;
     try {
@@ -1149,6 +1083,7 @@ export class ChargingStation {
     stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate);
     stationInfo.ocppVersion = stationTemplate?.ocppVersion ?? OCPPVersion.VERSION_16;
     createSerialNumber(stationTemplate, stationInfo);
+    stationInfo.voltageOut = this.getVoltageOut(stationInfo);
     if (isNotEmptyArray(stationTemplate?.power)) {
       stationTemplate.power = stationTemplate.power as number[];
       const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length);
@@ -1163,6 +1098,7 @@ export class ChargingStation {
           ? stationTemplate.power * 1000
           : stationTemplate.power;
     }
+    stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
     stationInfo.firmwareVersionPattern =
       stationTemplate?.firmwareVersionPattern ?? Constants.SEMVER_PATTERN;
     if (
@@ -1187,13 +1123,14 @@ export class ChargingStation {
     stationInfo.resetTime = !isNullOrUndefined(stationTemplate?.resetTime)
       ? secondsToMilliseconds(stationTemplate.resetTime!)
       : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
-    stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
     return stationInfo;
   }
 
-  private getStationInfoFromFile(): ChargingStationInfo | undefined {
+  private getStationInfoFromFile(
+    stationInfoPersistentConfiguration = true,
+  ): ChargingStationInfo | undefined {
     let stationInfo: ChargingStationInfo | undefined;
-    if (this.getStationInfoPersistentConfiguration()) {
+    if (stationInfoPersistentConfiguration === true) {
       stationInfo = this.getConfigurationFromFile()?.stationInfo;
       if (stationInfo) {
         delete stationInfo?.infoHash;
@@ -1203,13 +1140,16 @@ export class ChargingStation {
   }
 
   private getStationInfo(): ChargingStationInfo {
+    const defaultStationInfo = Constants.DEFAULT_STATION_INFO;
     const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate();
-    const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile();
+    const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile(
+      stationInfoFromTemplate?.stationInfoPersistentConfiguration,
+    );
     // Priority:
     // 1. charging station info from template
     // 2. charging station info from configuration file
     if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) {
-      return stationInfoFromFile!;
+      return { ...defaultStationInfo, ...stationInfoFromFile! };
     }
     stationInfoFromFile &&
       propagateSerialNumber(
@@ -1217,28 +1157,16 @@ export class ChargingStation {
         stationInfoFromFile,
         stationInfoFromTemplate,
       );
-    return stationInfoFromTemplate;
+    return { ...defaultStationInfo, ...stationInfoFromTemplate };
   }
 
   private saveStationInfo(): void {
-    if (this.getStationInfoPersistentConfiguration()) {
+    if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
       this.saveConfiguration();
     }
   }
 
-  private getOcppPersistentConfiguration(): boolean {
-    return this.stationInfo?.ocppPersistentConfiguration ?? true;
-  }
-
-  private getStationInfoPersistentConfiguration(): boolean {
-    return this.stationInfo?.stationInfoPersistentConfiguration ?? true;
-  }
-
-  private getAutomaticTransactionGeneratorPersistentConfiguration(): boolean {
-    return this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration ?? true;
-  }
-
-  private handleUnsupportedVersion(version: OCPPVersion) {
+  private handleUnsupportedVersion(version: OCPPVersion | undefined) {
     const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
     logger.error(`${this.logPrefix()} ${errorMsg}`);
     throw new BaseError(errorMsg);
@@ -1251,13 +1179,14 @@ export class ChargingStation {
       dirname(this.templateFile.replace('station-templates', 'configurations')),
       `${getHashId(this.index, stationTemplate)}.json`,
     );
-    const chargingStationConfiguration = this.getConfigurationFromFile();
+    const stationConfiguration = this.getConfigurationFromFile();
     if (
-      chargingStationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+      stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
       // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-      (chargingStationConfiguration?.connectorsStatus || chargingStationConfiguration?.evsesStatus)
+      (stationConfiguration?.connectorsStatus || stationConfiguration?.evsesStatus)
     ) {
-      this.initializeConnectorsOrEvsesFromFile(chargingStationConfiguration);
+      checkConfiguration(stationConfiguration, this.logPrefix(), this.configurationFile);
+      this.initializeConnectorsOrEvsesFromFile(stationConfiguration);
     } else {
       this.initializeConnectorsOrEvsesFromTemplate(stationTemplate);
     }
@@ -1270,19 +1199,21 @@ export class ChargingStation {
       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] = (
-        convertToInt(match[patchLevelIndex]) +
-        this.stationInfo.firmwareUpgrade!.versionUpgrade!.step!
-      ).toString();
-      this.stationInfo.firmwareVersion = match?.join('.');
+      const match = new RegExp(this.stationInfo.firmwareVersionPattern!)
+        .exec(this.stationInfo.firmwareVersion!)
+        ?.slice(1, patternGroup! + 1);
+      if (!isNullOrUndefined(match)) {
+        const patchLevelIndex = match!.length - 1;
+        match![patchLevelIndex] = (
+          convertToInt(match![patchLevelIndex]) +
+          this.stationInfo.firmwareUpgrade!.versionUpgrade!.step!
+        ).toString();
+        this.stationInfo.firmwareVersion = match!.join('.');
+      }
     }
     this.saveStationInfo();
     this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
-    if (this.getEnableStatistics() === true) {
+    if (this.stationInfo?.enableStatistics === true) {
       this.performanceStatistics = PerformanceStatistics.getInstance(
         this.stationInfo.hashId,
         this.stationInfo.chargingStationId!,
@@ -1295,6 +1226,11 @@ export class ChargingStation {
     this.ocppConfiguration = this.getOcppConfiguration();
     this.initializeOcppConfiguration();
     this.initializeOcppServices();
+    this.once(ChargingStationEvents.accepted, () => {
+      this.startMessageSequence().catch((error) => {
+        logger.error(`${this.logPrefix()} Error while starting the message sequence:`, error);
+      });
+    });
     if (this.stationInfo?.autoRegister === true) {
       this.bootNotificationResponse = {
         currentTime: new Date(),
@@ -1305,7 +1241,7 @@ export class ChargingStation {
   }
 
   private initializeOcppServices(): void {
-    const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
+    const ocppVersion = this.stationInfo?.ocppVersion;
     switch (ocppVersion) {
       case OCPPVersion.VERSION_16:
         this.ocppIncomingRequestService =
@@ -1329,33 +1265,33 @@ export class ChargingStation {
   }
 
   private initializeOcppConfiguration(): void {
-    if (!getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)) {
+    if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.HeartbeatInterval))) {
       addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0');
     }
-    if (!getConfigurationKey(this, StandardParametersKey.HeartBeatInterval)) {
+    if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.HeartBeatInterval))) {
       addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', { visible: false });
     }
     if (
-      this.getSupervisionUrlOcppConfiguration() &&
-      isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
-      !getConfigurationKey(this, this.getSupervisionUrlOcppKey())
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+      isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
+      isNullOrUndefined(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!))
     ) {
       addConfigurationKey(
         this,
-        this.getSupervisionUrlOcppKey(),
+        this.stationInfo.supervisionUrlOcppKey!,
         this.configuredSupervisionUrl.href,
         { reboot: true },
       );
     } else if (
-      !this.getSupervisionUrlOcppConfiguration() &&
-      isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
-      getConfigurationKey(this, this.getSupervisionUrlOcppKey())
+      this.stationInfo?.supervisionUrlOcppConfiguration === false &&
+      isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
+      !isNullOrUndefined(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!))
     ) {
-      deleteConfigurationKey(this, this.getSupervisionUrlOcppKey(), { save: false });
+      deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!, { save: false });
     }
     if (
       isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      !getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)
+      isNullOrUndefined(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!))
     ) {
       addConfigurationKey(
         this,
@@ -1365,7 +1301,9 @@ export class ChargingStation {
         ).toString(),
       );
     }
-    if (!getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)) {
+    if (
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles))
+    ) {
       addConfigurationKey(
         this,
         StandardParametersKey.SupportedFeatureProfiles,
@@ -1379,14 +1317,18 @@ export class ChargingStation {
       { readonly: true },
       { overwrite: true },
     );
-    if (!getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData)) {
+    if (
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData))
+    ) {
       addConfigurationKey(
         this,
         StandardParametersKey.MeterValuesSampledData,
         MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
       );
     }
-    if (!getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation)) {
+    if (
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation))
+    ) {
       const connectorsPhaseRotation: string[] = [];
       if (this.hasEvses) {
         for (const evseStatus of this.evses.values()) {
@@ -1409,18 +1351,20 @@ export class ChargingStation {
         connectorsPhaseRotation.toString(),
       );
     }
-    if (!getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests)) {
+    if (
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests))
+    ) {
       addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true');
     }
     if (
-      !getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) &&
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled)) &&
       getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)?.value?.includes(
         SupportedFeatureProfiles.LocalAuthListManagement,
       )
     ) {
       addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false');
     }
-    if (!getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)) {
+    if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut))) {
       addConfigurationKey(
         this,
         StandardParametersKey.ConnectionTimeOut,
@@ -1642,7 +1586,7 @@ export class ChargingStation {
   }
 
   private saveAutomaticTransactionGeneratorConfiguration(): void {
-    if (this.getAutomaticTransactionGeneratorPersistentConfiguration()) {
+    if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true) {
       this.saveConfiguration();
     }
   }
@@ -1664,13 +1608,16 @@ export class ChargingStation {
         let configurationData: ChargingStationConfiguration = this.getConfigurationFromFile()
           ? cloneObject<ChargingStationConfiguration>(this.getConfigurationFromFile()!)
           : {};
-        if (this.getStationInfoPersistentConfiguration() && this.stationInfo) {
+        if (this.stationInfo?.stationInfoPersistentConfiguration === true && this.stationInfo) {
           configurationData.stationInfo = this.stationInfo;
         } else {
           delete configurationData.stationInfo;
         }
-        if (this.getOcppPersistentConfiguration() && this.ocppConfiguration?.configurationKey) {
-          configurationData.configurationKey = this.ocppConfiguration.configurationKey;
+        if (
+          this.stationInfo?.ocppPersistentConfiguration === true &&
+          Array.isArray(this.ocppConfiguration?.configurationKey)
+        ) {
+          configurationData.configurationKey = this.ocppConfiguration?.configurationKey;
         } else {
           delete configurationData.configurationKey;
         }
@@ -1679,7 +1626,7 @@ export class ChargingStation {
           buildChargingStationAutomaticTransactionGeneratorConfiguration(this),
         );
         if (
-          !this.getAutomaticTransactionGeneratorPersistentConfiguration() ||
+          !this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration ||
           !this.getAutomaticTransactionGeneratorConfiguration()
         ) {
           delete configurationData.automaticTransactionGenerator;
@@ -1758,7 +1705,7 @@ export class ChargingStation {
 
   private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined {
     const configurationKey = this.getConfigurationFromFile()?.configurationKey;
-    if (this.getOcppPersistentConfiguration() === true && configurationKey) {
+    if (this.stationInfo?.ocppPersistentConfiguration === true && Array.isArray(configurationKey)) {
       return { configurationKey };
     }
     return undefined;
@@ -1778,9 +1725,9 @@ export class ChargingStation {
       logger.info(
         `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`,
       );
+      let registrationRetryCount = 0;
       if (this.isRegistered() === false) {
         // Send BootNotification
-        let registrationRetryCount = 0;
         do {
           this.bootNotificationResponse = await this.ocppRequestService.requestHandler<
             BootNotificationRequest,
@@ -1789,7 +1736,7 @@ export class ChargingStation {
             skipBufferingOnError: true,
           });
           if (this.isRegistered() === false) {
-            this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount;
+            this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount;
             await sleep(
               this?.bootNotificationResponse?.interval
                 ? secondsToMilliseconds(this.bootNotificationResponse.interval)
@@ -1798,22 +1745,23 @@ export class ChargingStation {
           }
         } while (
           this.isRegistered() === false &&
-          (registrationRetryCount <= this.getRegistrationMaxRetries()! ||
-            this.getRegistrationMaxRetries() === -1)
+          (registrationRetryCount <= this.stationInfo.registrationMaxRetries! ||
+            this.stationInfo?.registrationMaxRetries === -1)
         );
       }
       if (this.isRegistered() === true) {
+        this.emit(ChargingStationEvents.registered);
         if (this.inAcceptedState() === true) {
-          await this.startMessageSequence();
+          this.emit(ChargingStationEvents.accepted);
         }
       } else {
         logger.error(
-          `${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`,
+          `${this.logPrefix()} Registration failure: maximum retries reached (${registrationRetryCount}) or retry disabled (${this
+            .stationInfo?.registrationMaxRetries})`,
         );
       }
-      this.wsConnectionRestarted = false;
       this.autoReconnectRetryCount = 0;
-      parentPort?.postMessage(buildUpdatedMessage(this));
+      this.emit(ChargingStationEvents.updated);
     } else {
       logger.warn(
         `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`,
@@ -1821,7 +1769,7 @@ export class ChargingStation {
     }
   }
 
-  private async onClose(code: number, reason: Buffer): Promise<void> {
+  private async onClose(code: WebSocketCloseEventStatusCode, reason: Buffer): Promise<void> {
     switch (code) {
       // Normal close
       case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
@@ -1843,7 +1791,7 @@ export class ChargingStation {
         this.started === true && (await this.reconnect());
         break;
     }
-    parentPort?.postMessage(buildUpdatedMessage(this));
+    this.emit(ChargingStationEvents.updated);
   }
 
   private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined {
@@ -1853,17 +1801,17 @@ export class ChargingStation {
     }
     throw new OCPPError(
       ErrorType.PROTOCOL_ERROR,
-      `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString(
+      `Cached request for message id ${messageId} ${getMessageTypeString(
         messageType,
       )} is not an array`,
       undefined,
-      cachedRequest as JsonType,
+      cachedRequest,
     );
   }
 
   private async handleIncomingMessage(request: IncomingRequest): Promise<void> {
     const [messageType, messageId, commandName, commandPayload] = request;
-    if (this.getEnableStatistics() === true) {
+    if (this.stationInfo?.enableStatistics === true) {
       this.performanceStatistics?.addRequestStatistic(commandName, messageType);
     }
     logger.debug(
@@ -1878,6 +1826,7 @@ export class ChargingStation {
       commandName,
       commandPayload,
     );
+    this.emit(ChargingStationEvents.updated);
   }
 
   private handleResponseMessage(response: Response): void {
@@ -1926,7 +1875,7 @@ export class ChargingStation {
 
   private async onMessage(data: RawData): Promise<void> {
     let request: IncomingRequest | Response | ErrorResponse | undefined;
-    let messageType: number | undefined;
+    let messageType: MessageType | undefined;
     let errorMsg: string;
     try {
       // eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -1954,7 +1903,6 @@ export class ChargingStation {
             logger.error(`${this.logPrefix()} ${errorMsg}`);
             throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg);
         }
-        parentPort?.postMessage(buildUpdatedMessage(this));
       } else {
         throw new OCPPError(
           ErrorType.PROTOCOL_ERROR,
@@ -2025,7 +1973,7 @@ export class ChargingStation {
   }
 
   private getEnergyActiveImportRegister(connectorStatus: ConnectorStatus, rounded = false): number {
-    if (this.getMeteringPerTransaction() === true) {
+    if (this.stationInfo?.meteringPerTransaction === true) {
       return (
         (rounded === true
           ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue!)
@@ -2066,35 +2014,25 @@ export class ChargingStation {
 
   // 0 for disabling
   private getConnectionTimeout(): number {
-    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)) {
-      return (
-        parseInt(getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)!.value!) ??
-        Constants.DEFAULT_CONNECTION_TIMEOUT
+    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) !== undefined) {
+      return convertToInt(
+        getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)!.value! ??
+          Constants.DEFAULT_CONNECTION_TIMEOUT,
       );
     }
     return Constants.DEFAULT_CONNECTION_TIMEOUT;
   }
 
-  // -1 for unlimited, 0 for disabling
-  private getAutoReconnectMaxRetries(): number | undefined {
-    return this.stationInfo.autoReconnectMaxRetries ?? -1;
-  }
-
-  // -1 for unlimited, 0 for disabling
-  private getRegistrationMaxRetries(): number | undefined {
-    return this.stationInfo.registrationMaxRetries ?? -1;
-  }
-
   private getPowerDivider(): number {
     let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors();
-    if (this.stationInfo?.powerSharedByConnectors) {
+    if (this.stationInfo?.powerSharedByConnectors === true) {
       powerDivider = this.getNumberOfRunningTransactions();
     }
     return powerDivider;
   }
 
-  private getMaximumAmperage(stationInfo: ChargingStationInfo): number | undefined {
-    const maximumPower = this.getMaximumPower(stationInfo);
+  private getMaximumAmperage(stationInfo?: ChargingStationInfo): number | undefined {
+    const maximumPower = (stationInfo ?? this.stationInfo).maximumPower!;
     switch (this.getCurrentOutType(stationInfo)) {
       case CurrentType.AC:
         return ACElectricUtils.amperagePerPhaseFromPower(
@@ -2107,10 +2045,21 @@ export class ChargingStation {
     }
   }
 
+  private getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType {
+    return (stationInfo ?? this.stationInfo).currentOutType ?? CurrentType.AC;
+  }
+
+  private getVoltageOut(stationInfo?: ChargingStationInfo): Voltage {
+    return (
+      (stationInfo ?? this.stationInfo).voltageOut ??
+      getDefaultVoltageOut(this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile)
+    );
+  }
+
   private getAmperageLimitation(): number | undefined {
     if (
       isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)
+      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!) !== undefined
     ) {
       return (
         convertToInt(
@@ -2139,12 +2088,7 @@ export class ChargingStation {
         if (evseId > 0) {
           for (const [connectorId, connectorStatus] of evseStatus.connectors) {
             const connectorBootStatus = getBootConnectorStatus(this, connectorId, connectorStatus);
-            await OCPPServiceUtils.sendAndSetConnectorStatus(
-              this,
-              connectorId,
-              connectorBootStatus,
-              evseId,
-            );
+            await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus, evseId);
           }
         }
       }
@@ -2156,11 +2100,11 @@ export class ChargingStation {
             connectorId,
             this.getConnectorStatus(connectorId)!,
           );
-          await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorBootStatus);
+          await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus);
         }
       }
     }
-    if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
+    if (this.stationInfo.firmwareStatus === FirmwareStatus.Installing) {
       await this.ocppRequestService.requestHandler<
         FirmwareStatusNotificationRequest,
         FirmwareStatusNotificationResponse
@@ -2171,26 +2115,26 @@ export class ChargingStation {
     }
 
     // Start the ATG
-    if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
+    if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) {
       this.startAutomaticTransactionGenerator();
     }
-    this.wsConnectionRestarted === true && this.flushMessageBuffer();
+    this.flushMessageBuffer();
   }
 
   private async stopMessageSequence(
     reason?: StopTransactionReason,
-    stopTransactions = true,
+    stopTransactions = this.stationInfo?.stopTransactionsOnStopped,
   ): Promise<void> {
     // Stop WebSocket ping
     this.stopWebSocketPing();
     // Stop heartbeat
     this.stopHeartbeat();
-    // Stop ongoing transactions
-    stopTransactions && (await this.stopRunningTransactions(reason));
     // Stop the ATG
     if (this.automaticTransactionGenerator?.started === true) {
       this.stopAutomaticTransactionGenerator();
     }
+    // Stop ongoing transactions
+    stopTransactions && (await this.stopRunningTransactions(reason));
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
         if (evseId > 0) {
@@ -2201,7 +2145,7 @@ export class ChargingStation {
             >(
               this,
               RequestCommand.STATUS_NOTIFICATION,
-              OCPPServiceUtils.buildStatusNotificationRequest(
+              buildStatusNotificationRequest(
                 this,
                 connectorId,
                 ConnectorStatusEnum.Unavailable,
@@ -2221,11 +2165,7 @@ export class ChargingStation {
           >(
             this,
             RequestCommand.STATUS_NOTIFICATION,
-            OCPPServiceUtils.buildStatusNotificationRequest(
-              this,
-              connectorId,
-              ConnectorStatusEnum.Unavailable,
-            ),
+            buildStatusNotificationRequest(this, connectorId, ConnectorStatusEnum.Unavailable),
           );
           delete this.getConnectorStatus(connectorId)?.status;
         }
@@ -2234,13 +2174,13 @@ export class ChargingStation {
   }
 
   private startWebSocketPing(): void {
-    const webSocketPingInterval: number = getConfigurationKey(
-      this,
-      StandardParametersKey.WebSocketPingInterval,
-    )
-      ? convertToInt(getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value)
-      : 0;
-    if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
+    const webSocketPingInterval: number =
+      getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) !== undefined
+        ? convertToInt(
+            getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value,
+          )
+        : 0;
+    if (webSocketPingInterval > 0 && this.webSocketPingSetInterval === undefined) {
       this.webSocketPingSetInterval = setInterval(() => {
         if (this.isWebSocketConnectionOpened() === true) {
           this.wsConnection?.ping();
@@ -2251,7 +2191,7 @@ export class ChargingStation {
           webSocketPingInterval,
         )}`,
       );
-    } else if (this.webSocketPingSetInterval) {
+    } else if (this.webSocketPingSetInterval !== undefined) {
       logger.info(
         `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds(
           webSocketPingInterval,
@@ -2265,7 +2205,7 @@ export class ChargingStation {
   }
 
   private stopWebSocketPing(): void {
-    if (this.webSocketPingSetInterval) {
+    if (this.webSocketPingSetInterval !== undefined) {
       clearInterval(this.webSocketPingSetInterval);
       delete this.webSocketPingSetInterval;
     }
@@ -2310,7 +2250,7 @@ export class ChargingStation {
   }
 
   private stopHeartbeat(): void {
-    if (this.heartbeatSetInterval) {
+    if (this.heartbeatSetInterval !== undefined) {
       clearInterval(this.heartbeatSetInterval);
       delete this.heartbeatSetInterval;
     }
@@ -2323,10 +2263,6 @@ export class ChargingStation {
     }
   }
 
-  private getReconnectExponentialDelay(): boolean {
-    return this.stationInfo?.reconnectExponentialDelay ?? false;
-  }
-
   private async reconnect(): Promise<void> {
     // Stop WebSocket ping
     this.stopWebSocketPing();
@@ -2337,13 +2273,14 @@ export class ChargingStation {
       this.stopAutomaticTransactionGenerator();
     }
     if (
-      this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries()! ||
-      this.getAutoReconnectMaxRetries() === -1
+      this.autoReconnectRetryCount < this.stationInfo.autoReconnectMaxRetries! ||
+      this.stationInfo?.autoReconnectMaxRetries === -1
     ) {
       ++this.autoReconnectRetryCount;
-      const reconnectDelay = this.getReconnectExponentialDelay()
-        ? exponentialDelay(this.autoReconnectRetryCount)
-        : secondsToMilliseconds(this.getConnectionTimeout());
+      const reconnectDelay =
+        this.stationInfo?.reconnectExponentialDelay === true
+          ? exponentialDelay(this.autoReconnectRetryCount)
+          : secondsToMilliseconds(this.getConnectionTimeout());
       const reconnectDelayWithdraw = 1000;
       const reconnectTimeout =
         reconnectDelay && reconnectDelay - reconnectDelayWithdraw > 0
@@ -2365,12 +2302,11 @@ export class ChargingStation {
         },
         { closeOpened: true },
       );
-      this.wsConnectionRestarted = true;
-    } else if (this.getAutoReconnectMaxRetries() !== -1) {
+    } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) {
       logger.error(
         `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${
           this.autoReconnectRetryCount
-        }) or retries disabled (${this.getAutoReconnectMaxRetries()})`,
+        }) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries})`,
       );
     }
   }