refactor(simulator): factor out supervision urls distribution
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 84930a5f628be1f99ed0e010778b55474961729c..591b4644e1bdb805002f92ec9c30ef7e1ed72324 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 { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
-import { ChargingStationUtils } from './ChargingStationUtils';
-import ChargingStationWorkerBroadcastChannel from './ChargingStationWorkerBroadcastChannel';
-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';
+  AuthorizedTagsCache,
+  AutomaticTransactionGenerator,
+  ChargingStationConfigurationUtils,
+  ChargingStationUtils,
+  ChargingStationWorkerBroadcastChannel,
+  MessageChannelUtils,
+  SharedLRUCache,
+} from './internal';
 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 { OCPP16IncomingRequestService } from './ocpp/1.6/OCPP16IncomingRequestService';
+import { OCPP16ResponseService } from './ocpp/1.6/OCPP16ResponseService';
+import { OCPP20ResponseService } from './ocpp/2.0/OCPP20ResponseService';
+import { OCPPServiceUtils } from './ocpp/OCPPServiceUtils';
+import { BaseError, OCPPError } from '../exception';
+import { PerformanceStatistics } from '../performance';
 import {
+  type AutomaticTransactionGeneratorConfiguration,
   AvailabilityType,
   type BootNotificationRequest,
+  type BootNotificationResponse,
   type CachedRequest,
+  type ChargingStationConfiguration,
+  type ChargingStationInfo,
+  type ChargingStationOcppConfiguration,
+  type ChargingStationTemplate,
+  ConnectorPhaseRotation,
+  ConnectorStatus,
+  ConnectorStatusEnum,
+  CurrentType,
   type ErrorCallback,
+  type ErrorResponse,
+  ErrorType,
+  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,
+  type ResponseCallback,
+  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,
+  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 automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
+  public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
+  public wsConnection!: WebSocket | null;
   public readonly connectors: Map<number, ConnectorStatus>;
   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;
@@ -120,7 +126,7 @@ export default class ChargingStation {
   private configuredSupervisionUrlIndex!: number;
   private wsConnectionRestarted: boolean;
   private autoReconnectRetryCount: number;
-  private templateFileWatcher!: fs.FSWatcher;
+  private templateFileWatcher!: fs.FSWatcher | undefined;
   private readonly sharedLRUCache: SharedLRUCache;
   private webSocketPingSetInterval!: NodeJS.Timeout;
   private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
@@ -145,28 +151,30 @@ export default class ChargingStation {
 
   private get wsConnectionUrl(): URL {
     return new URL(
-      (this.getSupervisionUrlOcppConfiguration()
-        ? ChargingStationConfigurationUtils.getConfigurationKey(
-            this,
-            this.getSupervisionUrlOcppKey()
-          ).value
-        : this.configuredSupervisionUrl.href) +
-        '/' +
-        this.stationInfo.chargingStationId
+      `${
+        this.getSupervisionUrlOcppConfiguration()
+          ? 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()) ??
+        ''
       } |`
     );
-  }
+  };
 
   public hasAuthorizedTags(): boolean {
-    return !Utils.isEmptyArray(
+    return Utils.isNotEmptyArray(
       this.authorizedTagsCache.getAuthorizedTags(
         ChargingStationUtils.getAuthorizationFile(this.stationInfo)
       )
@@ -201,7 +209,7 @@ export default class ChargingStation {
     return this?.wsConnection?.readyState === WebSocket.OPEN;
   }
 
-  public getRegistrationStatus(): RegistrationStatusEnumType {
+  public getRegistrationStatus(): RegistrationStatusEnumType | undefined {
     return this?.bootNotificationResponse?.status;
   }
 
@@ -229,11 +237,11 @@ export default class ChargingStation {
   }
 
   public isChargingStationAvailable(): boolean {
-    return this.getConnectorStatus(0).availability === AvailabilityType.OPERATIVE;
+    return this.getConnectorStatus(0)?.availability === AvailabilityType.OPERATIVE;
   }
 
   public isConnectorAvailable(id: number): boolean {
-    return id > 0 && this.getConnectorStatus(id).availability === AvailabilityType.OPERATIVE;
+    return id > 0 && this.getConnectorStatus(id)?.availability === AvailabilityType.OPERATIVE;
   }
 
   public getNumberOfConnectors(): number {
@@ -245,7 +253,7 @@ export default class ChargingStation {
   }
 
   public getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType {
-    return (stationInfo ?? this.stationInfo).currentOutType ?? CurrentType.AC;
+    return (stationInfo ?? this.stationInfo)?.currentOutType ?? CurrentType.AC;
   }
 
   public getOcppStrictCompliance(): boolean {
@@ -273,7 +281,7 @@ export default class ChargingStation {
     let connectorAmperageLimitationPowerLimit: number;
     if (
       !Utils.isNullOrUndefined(this.getAmperageLimitation()) &&
-      this.getAmperageLimitation() < this.stationInfo.maximumAmperage
+      this.getAmperageLimitation() < this.stationInfo?.maximumAmperage
     ) {
       connectorAmperageLimitationPowerLimit =
         (this.getCurrentOutType() === CurrentType.AC
@@ -299,8 +307,11 @@ export default class ChargingStation {
 
   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 (
+        connectorId > 0 &&
+        this.getConnectorStatus(connectorId)?.transactionId === transactionId
+      ) {
+        return this.getConnectorStatus(connectorId)?.transactionIdTag;
       }
     }
   }
@@ -393,15 +404,15 @@ 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(
@@ -448,7 +459,7 @@ export default class ChargingStation {
       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`
@@ -470,7 +481,7 @@ export default class ChargingStation {
             RequestCommand.METER_VALUES,
             {
               connectorId,
-              transactionId: this.getConnectorStatus(connectorId).transactionId,
+              transactionId: this.getConnectorStatus(connectorId)?.transactionId,
               meterValue: [meterValue],
             }
           )
@@ -497,17 +508,17 @@ export default class ChargingStation {
       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} ${
@@ -525,9 +536,9 @@ export default class ChargingStation {
                   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) {
@@ -540,7 +551,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...`);
@@ -557,14 +568,14 @@ 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.templateFileWatcher?.close();
         this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
-        this.bootNotificationResponse = null;
+        this.bootNotificationResponse = undefined;
         this.started = false;
-        parentPort.postMessage(MessageChannelUtils.buildStoppedMessage(this));
+        parentPort?.postMessage(MessageChannelUtils.buildStoppedMessage(this));
         this.stopping = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already stopping...`);
@@ -592,21 +603,21 @@ export default class ChargingStation {
     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;
+    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;
+    delete this.getConnectorStatus(connectorId)?.transactionBeginMeterValue;
     this.stopMeterValues(connectorId);
-    parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    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 {
@@ -614,7 +625,7 @@ export default class ChargingStation {
   }
 
   public openWSConnection(
-    options: WsOptions = this.stationInfo?.wsOptions ?? {},
+    options: WsOptions = this.stationInfo?.wsOptions ?? Constants.EMPTY_OBJECT,
     params: { closeOpened?: boolean; terminateOpened?: boolean } = {
       closeOpened: false,
       terminateOpened: false,
@@ -647,7 +658,7 @@ export default class ChargingStation {
       case OCPPVersion.VERSION_16:
       case OCPPVersion.VERSION_20:
       case OCPPVersion.VERSION_201:
-        protocol = 'ocpp' + ocppVersion;
+        protocol = `ocpp${ocppVersion}`;
         break;
       default:
         this.handleUnsupportedVersion(ocppVersion);
@@ -692,7 +703,7 @@ export default class ChargingStation {
 
   public closeWSConnection(): void {
     if (this.isWebSocketConnectionOpened() === true) {
-      this.wsConnection.close();
+      this.wsConnection?.close();
       this.wsConnection = null;
     }
   }
@@ -706,32 +717,32 @@ export default class ChargingStation {
         this.getAutomaticTransactionGeneratorConfigurationFromTemplate(),
       this
     );
-    if (!Utils.isEmptyArray(connectorIds)) {
+    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));
+    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));
+    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 &&
@@ -767,8 +778,21 @@ 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);
+        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);
       });
     }
@@ -779,11 +803,11 @@ 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);
@@ -802,17 +826,17 @@ export default class ChargingStation {
       }
     } 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();
+    const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile();
     if (Utils.isNullOrUndefined(stationTemplate)) {
       const errorMsg = `Failed to read charging station template file ${this.templateFile}`;
       logger.error(`${this.logPrefix()} ${errorMsg}`);
@@ -843,26 +867,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(
@@ -871,13 +895,16 @@ 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 ?? Constants.EMPTY_OBJECT
     );
-    stationInfo.resetTime = stationTemplate.resetTime
+    stationInfo.resetTime = !Utils.isNullOrUndefined(stationTemplate?.resetTime)
       ? stationTemplate.resetTime * 1000
       : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
     const configuredMaxConnectors =
@@ -913,17 +940,17 @@ export default class ChargingStation {
     return stationInfo;
   }
 
-  private getStationInfoFromFile(): ChargingStationInfo | null {
-    let stationInfo: ChargingStationInfo = null;
+  private getStationInfoFromFile(): ChargingStationInfo | undefined {
+    let stationInfo: ChargingStationInfo | undefined;
     this.getStationInfoPersistentConfiguration() &&
-      (stationInfo = this.getConfigurationFromFile()?.stationInfo ?? null);
+      (stationInfo = this.getConfigurationFromFile()?.stationInfo);
     stationInfo && ChargingStationUtils.createStationInfoHash(stationInfo);
     return stationInfo;
   }
 
   private getStationInfo(): ChargingStationInfo {
     const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate();
-    const stationInfoFromFile: ChargingStationInfo = this.getStationInfoFromFile();
+    const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile();
     // Priority: charging station info from template > charging station info from configuration file > charging station info attribute
     if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) {
       if (this.stationInfo?.infoHash === stationInfoFromFile?.infoHash) {
@@ -963,7 +990,7 @@ export default class ChargingStation {
   private initialize(): void {
     this.configurationFile = path.join(
       path.dirname(this.templateFile.replace('station-templates', 'configurations')),
-      ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile()) + '.json'
+      `${ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile())}.json`
     );
     this.stationInfo = this.getStationInfo();
     this.saveStationInfo();
@@ -1014,21 +1041,21 @@ export default class ChargingStation {
     }
     if (
       this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
-      this.stationInfo.firmwareVersion &&
-      this.stationInfo.firmwareVersionPattern
+      Utils.isNotEmptyString(this.stationInfo.firmwareVersion) &&
+      Utils.isNotEmptyString(this.stationInfo.firmwareVersionPattern)
     ) {
-      const versionStep = this.stationInfo.firmwareUpgrade?.versionUpgrade?.step ?? 1;
-      const patternGroup: number =
+      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);
+        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
+        Utils.convertToInt(match[patchLevelIndex]) +
+        this.stationInfo.firmwareUpgrade?.versionUpgrade?.step
       ).toString();
-      this.stationInfo.firmwareVersion = match.join('.');
+      this.stationInfo.firmwareVersion = match?.join('.');
     }
   }
 
@@ -1079,7 +1106,7 @@ export default class ChargingStation {
       );
     }
     if (
-      this.stationInfo.amperageLimitationOcppKey &&
+      Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
       !ChargingStationConfigurationUtils.getConfigurationKey(
         this,
         this.stationInfo.amperageLimitationOcppKey
@@ -1171,7 +1198,7 @@ export default class ChargingStation {
       ChargingStationConfigurationUtils.getConfigurationKey(
         this,
         StandardParametersKey.SupportedFeatureProfiles
-      )?.value.includes(SupportedFeatureProfiles.LocalAuthListManagement)
+      )?.value?.includes(SupportedFeatureProfiles.LocalAuthListManagement)
     ) {
       ChargingStationConfigurationUtils.addConfigurationKey(
         this,
@@ -1214,7 +1241,7 @@ export default class ChargingStation {
     if (stationInfo?.Connectors) {
       const connectorsConfigHash = crypto
         .createHash(Constants.DEFAULT_HASH_ALGORITHM)
-        .update(JSON.stringify(stationInfo?.Connectors) + configuredMaxConnectors.toString())
+        .update(`${JSON.stringify(stationInfo?.Connectors)}${configuredMaxConnectors.toString()}`)
         .digest('hex');
       const connectorsConfigChanged =
         this.connectors?.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash;
@@ -1267,17 +1294,17 @@ export default class ChargingStation {
     }
     // Initialize transaction attributes on connectors
     for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0 && this.getConnectorStatus(connectorId).transactionStarted === true) {
+      if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
         logger.warn(
           `${this.logPrefix()} Connector ${connectorId} at initialization has a transaction started: ${
-            this.getConnectorStatus(connectorId).transactionId
+            this.getConnectorStatus(connectorId)?.transactionId
           }`
         );
       }
       if (
         connectorId > 0 &&
-        (this.getConnectorStatus(connectorId).transactionStarted === undefined ||
-          this.getConnectorStatus(connectorId).transactionStarted === null)
+        (this.getConnectorStatus(connectorId)?.transactionStarted === undefined ||
+          this.getConnectorStatus(connectorId)?.transactionStarted === null)
       ) {
         this.initializeConnectorStatus(connectorId);
       }
@@ -1298,8 +1325,8 @@ export default class ChargingStation {
     }
   }
 
-  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)) {
@@ -1318,10 +1345,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()
         );
       }
     }
@@ -1335,7 +1362,7 @@ export default class ChargingStation {
           fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
         }
         const configurationData: ChargingStationConfiguration =
-          this.getConfigurationFromFile() ?? {};
+          Utils.cloneObject(this.getConfigurationFromFile()) ?? Constants.EMPTY_OBJECT;
         this.ocppConfiguration?.configurationKey &&
           (configurationData.configurationKey = this.ocppConfiguration.configurationKey);
         this.stationInfo && (configurationData.stationInfo = this.stationInfo);
@@ -1364,10 +1391,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 {
@@ -1377,22 +1404,26 @@ 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 {
+    let configuration: ChargingStationConfiguration | undefined;
     if (this.getOcppPersistentConfiguration() === true) {
       const configurationFromFile = this.getConfigurationFromFile();
       configuration = configurationFromFile?.configurationKey && configurationFromFile;
     }
-    configuration && delete configuration.stationInfo;
+    if (!Utils.isNullOrUndefined(configuration)) {
+      delete configuration.stationInfo;
+      delete configuration.configurationHash;
+    }
     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();
     }
@@ -1417,7 +1448,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
             );
@@ -1439,7 +1470,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`
@@ -1469,7 +1500,7 @@ export default class ChargingStation {
         this.started === true && (await this.reconnect());
         break;
     }
-    parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
   }
 
   private async onMessage(data: RawData): Promise<void> {
@@ -1496,7 +1527,7 @@ export default class ChargingStation {
           case MessageType.CALL_MESSAGE:
             [, , commandName, commandPayload] = request as IncomingRequest;
             if (this.getEnableStatistics() === true) {
-              this.performanceStatistics.addRequestStatistic(commandName, messageType);
+              this.performanceStatistics?.addRequestStatistic(commandName, messageType);
             }
             logger.debug(
               `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
@@ -1519,7 +1550,7 @@ export default class ChargingStation {
               throw new OCPPError(
                 ErrorType.INTERNAL_ERROR,
                 `Response for unknown message id ${messageId}`,
-                null,
+                undefined,
                 commandPayload
               );
             }
@@ -1531,7 +1562,7 @@ export default class ChargingStation {
               throw new OCPPError(
                 ErrorType.PROTOCOL_ERROR,
                 `Cached request for message id ${messageId} response is not an array`,
-                null,
+                undefined,
                 cachedRequest as unknown as JsonType
               );
             }
@@ -1550,7 +1581,7 @@ export default class ChargingStation {
               throw new OCPPError(
                 ErrorType.INTERNAL_ERROR,
                 `Error response for unknown message id ${messageId}`,
-                null,
+                undefined,
                 { errorType, errorMessage, errorDetails }
               );
             }
@@ -1561,7 +1592,7 @@ export default class ChargingStation {
               throw new OCPPError(
                 ErrorType.PROTOCOL_ERROR,
                 `Cached request for message id ${messageId} error response is not an array`,
-                null,
+                undefined,
                 cachedRequest as unknown as JsonType
               );
             }
@@ -1579,7 +1610,7 @@ export default class ChargingStation {
             logger.error(`${this.logPrefix()} ${errMsg}`);
             throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
         }
-        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,
@@ -1630,16 +1661,16 @@ export default class ChargingStation {
   }
 
   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, rounded = false): number {
@@ -1659,9 +1690,7 @@ export default class ChargingStation {
 
   private getUseConnectorId0(stationInfo?: ChargingStationInfo): boolean {
     const localStationInfo = stationInfo ?? this.stationInfo;
-    return !Utils.isUndefined(localStationInfo.useConnectorId0)
-      ? localStationInfo.useConnectorId0
-      : true;
+    return localStationInfo?.useConnectorId0 ?? true;
   }
 
   private getNumberOfRunningTransactions(): number {
@@ -1703,7 +1732,7 @@ export default class ChargingStation {
   }
 
   // -1 for unlimited, 0 for disabling
-  private getAutoReconnectMaxRetries(): number {
+  private getAutoReconnectMaxRetries(): number | undefined {
     if (!Utils.isUndefined(this.stationInfo.autoReconnectMaxRetries)) {
       return this.stationInfo.autoReconnectMaxRetries;
     }
@@ -1714,7 +1743,7 @@ export default class ChargingStation {
   }
 
   // 0 for disabling
-  private getRegistrationMaxRetries(): number {
+  private getRegistrationMaxRetries(): number | undefined {
     if (!Utils.isUndefined(this.stationInfo.registrationMaxRetries)) {
       return this.stationInfo.registrationMaxRetries;
     }
@@ -1745,7 +1774,7 @@ export default class ChargingStation {
 
   private getAmperageLimitation(): number | undefined {
     if (
-      this.stationInfo.amperageLimitationOcppKey &&
+      Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
       ChargingStationConfigurationUtils.getConfigurationKey(
         this,
         this.stationInfo.amperageLimitationOcppKey
@@ -1756,7 +1785,7 @@ export default class ChargingStation {
           ChargingStationConfigurationUtils.getConfigurationKey(
             this,
             this.stationInfo.amperageLimitationOcppKey
-          ).value
+          )?.value
         ) / ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
       );
     }
@@ -1777,7 +1806,7 @@ export default class ChargingStation {
     this.startHeartbeat();
     // Initialize connectors status
     for (const connectorId of this.connectors.keys()) {
-      let connectorStatus: ConnectorStatusEnum;
+      let connectorStatus: ConnectorStatusEnum | undefined;
       if (connectorId === 0) {
         continue;
       } else if (
@@ -1785,19 +1814,19 @@ export default class ChargingStation {
         (this.isChargingStationAvailable() === false ||
           this.isConnectorAvailable(connectorId) === false)
       ) {
-        connectorStatus = ConnectorStatusEnum.UNAVAILABLE;
+        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;
+        connectorStatus = this.getConnectorStatus(connectorId)?.bootStatus;
       } else if (this.getConnectorStatus(connectorId)?.status) {
         // Set previous status at startup
-        connectorStatus = this.getConnectorStatus(connectorId).status;
+        connectorStatus = this.getConnectorStatus(connectorId)?.status;
       } else {
         // Set default status
-        connectorStatus = ConnectorStatusEnum.AVAILABLE;
+        connectorStatus = ConnectorStatusEnum.Available;
       }
       await this.ocppRequestService.requestHandler<
         StatusNotificationRequest,
@@ -1850,10 +1879,10 @@ export default class ChargingStation {
           OCPPServiceUtils.buildStatusNotificationRequest(
             this,
             connectorId,
-            ConnectorStatusEnum.UNAVAILABLE
+            ConnectorStatusEnum.Unavailable
           )
         );
-        this.getConnectorStatus(connectorId).status = null;
+        this.getConnectorStatus(connectorId).status = undefined;
       }
     }
   }
@@ -1867,25 +1896,25 @@ 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(
@@ -1905,27 +1934,25 @@ export default class ChargingStation {
   }
 
   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)) {
       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
           );
           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
-            }`
-          );
+          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
+              }`
+            );
           this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
           break;
       }
@@ -1966,21 +1993,19 @@ export default class ChargingStation {
 
   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);
+      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> {
@@ -2013,10 +2038,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 ?? Constants.EMPTY_OBJECT),
+          handshakeTimeout: reconnectTimeout,
+        },
         { closeOpened: true }
       );
       this.wsConnectionRestarted = true;
@@ -2029,8 +2057,10 @@ export default class ChargingStation {
     }
   }
 
-  private getAutomaticTransactionGeneratorConfigurationFromTemplate(): AutomaticTransactionGeneratorConfiguration | null {
-    return this.getTemplateFromFile()?.AutomaticTransactionGenerator ?? null;
+  private getAutomaticTransactionGeneratorConfigurationFromTemplate():
+    | AutomaticTransactionGeneratorConfiguration
+    | undefined {
+    return this.getTemplateFromFile()?.AutomaticTransactionGenerator;
   }
 
   private initializeConnectorStatus(connectorId: number): void {