refactor(simulator): remove unneeded type casting
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index e531aa0f04bc5962518998a1affa76ded4b477c5..ac5aaa93c451d8c877c9d93b0345550b74dd37e8 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;
+        delete this.bootNotificationResponse;
         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,
@@ -641,18 +652,6 @@ export default class ChargingStation {
     if (params?.terminateOpened) {
       this.terminateWSConnection();
     }
-    const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
-    let protocol: string;
-    switch (ocppVersion) {
-      case OCPPVersion.VERSION_16:
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        protocol = 'ocpp' + ocppVersion;
-        break;
-      default:
-        this.handleUnsupportedVersion(ocppVersion);
-        break;
-    }
 
     if (this.isWebSocketConnectionOpened() === true) {
       logger.warn(
@@ -665,7 +664,11 @@ export default class ChargingStation {
       `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`
     );
 
-    this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
+    this.wsConnection = new WebSocket(
+      this.wsConnectionUrl,
+      `ocpp${this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16}`,
+      options
+    );
 
     // Handle WebSocket message
     this.wsConnection.on(
@@ -692,7 +695,7 @@ export default class ChargingStation {
 
   public closeWSConnection(): void {
     if (this.isWebSocketConnectionOpened() === true) {
-      this.wsConnection.close();
+      this.wsConnection?.close();
       this.wsConnection = null;
     }
   }
@@ -706,32 +709,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 +770,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 +795,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 +818,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 +859,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 +887,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 +932,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,12 +982,12 @@ 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();
     // Avoid duplication of connectors related information in RAM
-    this.stationInfo?.Connectors && delete this.stationInfo.Connectors;
+    delete this.stationInfo?.Connectors;
     this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
     if (this.getEnableStatistics() === true) {
       this.performanceStatistics = PerformanceStatistics.getInstance(
@@ -984,6 +1003,35 @@ export default class ChargingStation {
     // OCPP configuration
     this.ocppConfiguration = this.getOcppConfiguration();
     this.initializeOcppConfiguration();
+    this.initializeOcppServices();
+    if (this.stationInfo?.autoRegister === true) {
+      this.bootNotificationResponse = {
+        currentTime: new Date(),
+        interval: this.getHeartbeatInterval() / 1000,
+        status: RegistrationStatusEnumType.ACCEPTED,
+      };
+    }
+    if (
+      this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
+      Utils.isNotEmptyString(this.stationInfo.firmwareVersion) &&
+      Utils.isNotEmptyString(this.stationInfo.firmwareVersionPattern)
+    ) {
+      const patternGroup: number | undefined =
+        this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
+        this.stationInfo.firmwareVersion?.split('.').length;
+      const match = this.stationInfo?.firmwareVersion
+        ?.match(new RegExp(this.stationInfo.firmwareVersionPattern))
+        ?.slice(1, patternGroup + 1);
+      const patchLevelIndex = match.length - 1;
+      match[patchLevelIndex] = (
+        Utils.convertToInt(match[patchLevelIndex]) +
+        this.stationInfo.firmwareUpgrade?.versionUpgrade?.step
+      ).toString();
+      this.stationInfo.firmwareVersion = match?.join('.');
+    }
+  }
+
+  private initializeOcppServices(): void {
     const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
     switch (ocppVersion) {
       case OCPPVersion.VERSION_16:
@@ -1005,31 +1053,6 @@ export default class ChargingStation {
         this.handleUnsupportedVersion(ocppVersion);
         break;
     }
-    if (this.stationInfo?.autoRegister === true) {
-      this.bootNotificationResponse = {
-        currentTime: new Date(),
-        interval: this.getHeartbeatInterval() / 1000,
-        status: RegistrationStatusEnumType.ACCEPTED,
-      };
-    }
-    if (
-      this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
-      this.stationInfo.firmwareVersion &&
-      this.stationInfo.firmwareVersionPattern
-    ) {
-      const versionStep = this.stationInfo.firmwareUpgrade?.versionUpgrade?.step ?? 1;
-      const patternGroup: number =
-        this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
-        this.stationInfo.firmwareVersion.split('.').length;
-      const match = this.stationInfo.firmwareVersion
-        .match(new RegExp(this.stationInfo.firmwareVersionPattern))
-        .slice(1, patternGroup + 1);
-      const patchLevelIndex = match.length - 1;
-      match[patchLevelIndex] = (
-        Utils.convertToInt(match[patchLevelIndex]) + versionStep
-      ).toString();
-      this.stationInfo.firmwareVersion = match.join('.');
-    }
   }
 
   private initializeOcppConfiguration(): void {
@@ -1079,7 +1102,7 @@ export default class ChargingStation {
       );
     }
     if (
-      this.stationInfo.amperageLimitationOcppKey &&
+      Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
       !ChargingStationConfigurationUtils.getConfigurationKey(
         this,
         this.stationInfo.amperageLimitationOcppKey
@@ -1171,7 +1194,7 @@ export default class ChargingStation {
       ChargingStationConfigurationUtils.getConfigurationKey(
         this,
         StandardParametersKey.SupportedFeatureProfiles
-      )?.value.includes(SupportedFeatureProfiles.LocalAuthListManagement)
+      )?.value?.includes(SupportedFeatureProfiles.LocalAuthListManagement)
     ) {
       ChargingStationConfigurationUtils.addConfigurationKey(
         this,
@@ -1214,7 +1237,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 +1290,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);
       }
@@ -1294,12 +1317,12 @@ export default class ChargingStation {
           this.templateFile
         } with connector ${connectorId} status configuration defined, undefine it`
       );
-      connectorStatus.status = undefined;
+      delete connectorStatus.status;
     }
   }
 
-  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 +1341,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 +1358,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 +1387,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 +1400,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 +1444,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 +1466,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,155 +1496,137 @@ export default class ChargingStation {
         this.started === true && (await this.reconnect());
         break;
     }
-    parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+    parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+  }
+
+  private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined {
+    const cachedRequest = this.requests.get(messageId);
+    if (Array.isArray(cachedRequest) === true) {
+      return cachedRequest;
+    }
+    throw new OCPPError(
+      ErrorType.PROTOCOL_ERROR,
+      `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString(
+        messageType
+      )} is not an array`,
+      undefined,
+      cachedRequest as JsonType
+    );
+  }
+
+  private async handleIncomingMessage(request: IncomingRequest): Promise<void> {
+    const [messageType, messageId, commandName, commandPayload] = request;
+    if (this.getEnableStatistics() === true) {
+      this.performanceStatistics?.addRequestStatistic(commandName, messageType);
+    }
+    logger.debug(
+      `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
+        request
+      )}`
+    );
+    // Process the message
+    await this.ocppIncomingRequestService.incomingRequestHandler(
+      this,
+      messageId,
+      commandName,
+      commandPayload
+    );
+  }
+
+  private handleResponseMessage(response: Response): void {
+    const [messageType, messageId, commandPayload] = response;
+    if (this.requests.has(messageId) === false) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Response for unknown message id ${messageId}`,
+        undefined,
+        commandPayload
+      );
+    }
+    // Respond
+    const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest(
+      messageType,
+      messageId
+    );
+    logger.debug(
+      `${this.logPrefix()} << Command '${
+        requestCommandName ?? Constants.UNKNOWN_COMMAND
+      }' received response payload: ${JSON.stringify(response)}`
+    );
+    responseCallback(commandPayload, requestPayload);
+  }
+
+  private handleErrorMessage(errorResponse: ErrorResponse): void {
+    const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse;
+    if (this.requests.has(messageId) === false) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Error response for unknown message id ${messageId}`,
+        undefined,
+        { errorType, errorMessage, errorDetails }
+      );
+    }
+    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId);
+    logger.debug(
+      `${this.logPrefix()} << Command '${
+        requestCommandName ?? Constants.UNKNOWN_COMMAND
+      }' received error response payload: ${JSON.stringify(errorResponse)}`
+    );
+    errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails));
   }
 
   private async onMessage(data: RawData): Promise<void> {
+    let request: IncomingRequest | Response | ErrorResponse;
     let messageType: number;
-    let messageId: string;
-    let commandName: IncomingRequestCommand;
-    let commandPayload: JsonType;
-    let errorType: ErrorType;
-    let errorMessage: string;
-    let errorDetails: JsonType;
-    let responseCallback: ResponseCallback;
-    let errorCallback: ErrorCallback;
-    let requestCommandName: RequestCommand | IncomingRequestCommand;
-    let requestPayload: JsonType;
-    let cachedRequest: CachedRequest;
     let errMsg: string;
     try {
-      const request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
+      request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
       if (Array.isArray(request) === true) {
-        [messageType, messageId] = request;
+        [messageType] = request;
         // Check the type of message
         switch (messageType) {
           // Incoming Message
           case MessageType.CALL_MESSAGE:
-            [, , commandName, commandPayload] = request as IncomingRequest;
-            if (this.getEnableStatistics() === true) {
-              this.performanceStatistics.addRequestStatistic(commandName, messageType);
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
-                request
-              )}`
-            );
-            // Process the message
-            await this.ocppIncomingRequestService.incomingRequestHandler(
-              this,
-              messageId,
-              commandName,
-              commandPayload
-            );
+            await this.handleIncomingMessage(request as IncomingRequest);
             break;
-          // Outcome Message
+          // Response Message
           case MessageType.CALL_RESULT_MESSAGE:
-            [, , commandPayload] = request as Response;
-            if (this.requests.has(messageId) === false) {
-              // Error
-              throw new OCPPError(
-                ErrorType.INTERNAL_ERROR,
-                `Response for unknown message id ${messageId}`,
-                null,
-                commandPayload
-              );
-            }
-            // Respond
-            cachedRequest = this.requests.get(messageId);
-            if (Array.isArray(cachedRequest) === true) {
-              [responseCallback, errorCallback, requestCommandName, requestPayload] = cachedRequest;
-            } else {
-              throw new OCPPError(
-                ErrorType.PROTOCOL_ERROR,
-                `Cached request for message id ${messageId} response is not an array`,
-                null,
-                cachedRequest as unknown as JsonType
-              );
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${
-                requestCommandName ?? Constants.UNKNOWN_COMMAND
-              }' received response payload: ${JSON.stringify(request)}`
-            );
-            responseCallback(commandPayload, requestPayload);
+            this.handleResponseMessage(request as Response);
             break;
           // Error Message
           case MessageType.CALL_ERROR_MESSAGE:
-            [, , errorType, errorMessage, errorDetails] = request as ErrorResponse;
-            if (this.requests.has(messageId) === false) {
-              // Error
-              throw new OCPPError(
-                ErrorType.INTERNAL_ERROR,
-                `Error response for unknown message id ${messageId}`,
-                null,
-                { errorType, errorMessage, errorDetails }
-              );
-            }
-            cachedRequest = this.requests.get(messageId);
-            if (Array.isArray(cachedRequest) === true) {
-              [, errorCallback, requestCommandName] = cachedRequest;
-            } else {
-              throw new OCPPError(
-                ErrorType.PROTOCOL_ERROR,
-                `Cached request for message id ${messageId} error response is not an array`,
-                null,
-                cachedRequest as unknown as JsonType
-              );
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${
-                requestCommandName ?? Constants.UNKNOWN_COMMAND
-              }' received error payload: ${JSON.stringify(request)}`
-            );
-            errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails));
+            this.handleErrorMessage(request as ErrorResponse);
             break;
-          // Error
+          // Unknown Message
           default:
             // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
             errMsg = `Wrong message type ${messageType}`;
             logger.error(`${this.logPrefix()} ${errMsg}`);
             throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
         }
-        parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
+        parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
       } else {
         throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming message is not an array', null, {
           request,
         });
       }
     } catch (error) {
-      // Log
-      logger.error(
-        `${this.logPrefix()} Incoming OCPP command '${
-          commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
-        }' message '${data.toString()}'${
-          messageType !== MessageType.CALL_MESSAGE
-            ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
-            : ''
-        } processing error:`,
-        error
-      );
-      if (error instanceof OCPPError === false) {
-        logger.warn(
-          `${this.logPrefix()} Error thrown at incoming OCPP command '${
-            commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
-          }' message '${data.toString()}' handling is not an OCPPError:`,
-          error
-        );
-      }
+      let commandName: IncomingRequestCommand;
+      let requestCommandName: RequestCommand | IncomingRequestCommand;
+      let errorCallback: ErrorCallback;
+      const [, messageId] = request;
       switch (messageType) {
         case MessageType.CALL_MESSAGE:
+          [, , commandName] = request as IncomingRequest;
           // Send error
-          await this.ocppRequestService.sendError(
-            this,
-            messageId,
-            error as OCPPError,
-            commandName ?? requestCommandName ?? null
-          );
+          await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName);
           break;
         case MessageType.CALL_RESULT_MESSAGE:
         case MessageType.CALL_ERROR_MESSAGE:
-          if (errorCallback) {
+          if (this.requests.has(messageId) === true) {
+            [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId);
             // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
             errorCallback(error as OCPPError, false);
           } else {
@@ -1626,20 +1635,38 @@ export default class ChargingStation {
           }
           break;
       }
+      if (error instanceof OCPPError === false) {
+        logger.warn(
+          `${this.logPrefix()} Error thrown at incoming OCPP command '${
+            commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+          }' message '${data.toString()}' handling is not an OCPPError:`,
+          error
+        );
+      }
+      logger.error(
+        `${this.logPrefix()} Incoming OCPP command '${
+          commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+        }' message '${data.toString()}'${
+          messageType !== MessageType.CALL_MESSAGE
+            ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
+            : ''
+        } processing error:`,
+        error
+      );
     }
   }
 
   private onPing(): void {
-    logger.debug(this.logPrefix() + ' Received a WS ping (rfc6455) from the server');
+    logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`);
   }
 
   private onPong(): void {
-    logger.debug(this.logPrefix() + ' Received a WS pong (rfc6455) from the server');
+    logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`);
   }
 
   private onError(error: WSError): void {
     this.closeWSConnection();
-    logger.error(this.logPrefix() + ' WebSocket error:', error);
+    logger.error(`${this.logPrefix()} WebSocket error:`, error);
   }
 
   private getEnergyActiveImportRegister(connectorStatus: ConnectorStatus, rounded = false): number {
@@ -1659,9 +1686,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 +1728,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 +1739,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 +1770,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 +1781,7 @@ export default class ChargingStation {
           ChargingStationConfigurationUtils.getConfigurationKey(
             this,
             this.stationInfo.amperageLimitationOcppKey
-          ).value
+          )?.value
         ) / ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
       );
     }
@@ -1777,7 +1802,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 +1810,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 +1875,10 @@ export default class ChargingStation {
           OCPPServiceUtils.buildStatusNotificationRequest(
             this,
             connectorId,
-            ConnectorStatusEnum.UNAVAILABLE
+            ConnectorStatusEnum.Unavailable
           )
         );
-        this.getConnectorStatus(connectorId).status = null;
+        delete this.getConnectorStatus(connectorId)?.status;
       }
     }
   }
@@ -1867,25 +1892,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 +1930,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 +1989,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 +2034,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 +2053,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 {