Add return type to getLogger
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index ad4dac388a293b56243982184dd1f40848b7fbf3..1e10118bc86c5a0dec8d3e1c71a5becd4f118c4f 100644 (file)
@@ -20,6 +20,7 @@ import { ConnectorStatus } from '../types/ConnectorStatus';
 import Constants from '../utils/Constants';
 import { ErrorType } from '../types/ocpp/ErrorType';
 import FileUtils from '../utils/FileUtils';
+import { JsonType } from '../types/JsonType';
 import { MessageType } from '../types/ocpp/MessageType';
 import OCPP16IncomingRequestService from './ocpp/1.6/OCPP16IncomingRequestService';
 import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService';
@@ -31,11 +32,12 @@ import { OCPPVersion } from '../types/ocpp/OCPPVersion';
 import PerformanceStatistics from '../performance/PerformanceStatistics';
 import { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates';
 import { StopTransactionReason } from '../types/ocpp/Transaction';
+import { SupervisionUrlDistribution } from '../types/ConfigurationData';
 import { URL } from 'url';
 import Utils from '../utils/Utils';
 import crypto from 'crypto';
 import fs from 'fs';
-import logger from '../utils/Logger';
+import getLogger from '../utils/Logger';
 import { parentPort } from 'worker_threads';
 import path from 'path';
 
@@ -50,6 +52,7 @@ export default class ChargingStation {
   public performanceStatistics!: PerformanceStatistics;
   public heartbeatSetInterval!: NodeJS.Timeout;
   public ocppRequestService!: OCPPRequestService;
+  private readonly id: string;
   private readonly index: number;
   private bootNotificationRequest!: BootNotificationRequest;
   private bootNotificationResponse!: BootNotificationResponse | null;
@@ -64,6 +67,7 @@ export default class ChargingStation {
   private webSocketPingSetInterval!: NodeJS.Timeout;
 
   constructor(index: number, stationTemplateFile: string) {
+    this.id = Utils.generateUUID();
     this.index = index;
     this.stationTemplateFile = stationTemplateFile;
     this.connectors = new Map<number, ConnectorStatus>();
@@ -121,10 +125,30 @@ export default class ChargingStation {
     return this?.wsConnection?.readyState === OPEN;
   }
 
-  public isRegistered(): boolean {
+  public getRegistrationStatus(): RegistrationStatus {
+    return this?.bootNotificationResponse?.status;
+  }
+
+  public isInUnknownState(): boolean {
+    return Utils.isNullOrUndefined(this?.bootNotificationResponse?.status);
+  }
+
+  public isInPendingState(): boolean {
+    return this?.bootNotificationResponse?.status === RegistrationStatus.PENDING;
+  }
+
+  public isInAcceptedState(): boolean {
     return this?.bootNotificationResponse?.status === RegistrationStatus.ACCEPTED;
   }
 
+  public isInRejectedState(): boolean {
+    return this?.bootNotificationResponse?.status === RegistrationStatus.REJECTED;
+  }
+
+  public isRegistered(): boolean {
+    return !this.isInUnknownState() && (this.isInAcceptedState() || this.isInPendingState());
+  }
+
   public isChargingStationAvailable(): boolean {
     return this.getConnectorStatus(0).availability === AvailabilityType.OPERATIVE;
   }
@@ -145,6 +169,10 @@ export default class ChargingStation {
     return this.stationInfo.currentOutType ?? CurrentType.AC;
   }
 
+  public getOcppStrictCompliance(): boolean {
+    return this.stationInfo.ocppStrictCompliance ?? false;
+  }
+
   public getVoltageOut(): number | undefined {
     const errMsg = `${this.logPrefix()} Unknown ${this.getCurrentOutType()} currentOutType in template file ${this.stationTemplateFile}, cannot define default voltage out`;
     let defaultVoltageOut: number;
@@ -156,7 +184,7 @@ export default class ChargingStation {
         defaultVoltageOut = Voltage.VOLTAGE_400;
         break;
       default:
-        logger.error(errMsg);
+        getLogger().error(errMsg);
         throw new Error(errMsg);
     }
     return !Utils.isUndefined(this.stationInfo.voltageOut) ? this.stationInfo.voltageOut : defaultVoltageOut;
@@ -236,17 +264,17 @@ export default class ChargingStation {
   public getSampledValueTemplate(connectorId: number, measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
       phase?: MeterValuePhase): SampledValueTemplate | undefined {
     if (!Constants.SUPPORTED_MEASURANDS.includes(measurand)) {
-      logger.warn(`${this.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
+      getLogger().warn(`${this.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
       return;
     }
     if (measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && !this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(measurand)) {
-      logger.debug(`${this.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId} not found in '${StandardParametersKey.MeterValuesSampledData}' OCPP parameter`);
+      getLogger().debug(`${this.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId} not found in '${StandardParametersKey.MeterValuesSampledData}' OCPP parameter`);
       return;
     }
     const sampledValueTemplates: SampledValueTemplate[] = this.getConnectorStatus(connectorId).MeterValues;
     for (let index = 0; !Utils.isEmptyArray(sampledValueTemplates) && index < sampledValueTemplates.length; index++) {
       if (!Constants.SUPPORTED_MEASURANDS.includes(sampledValueTemplates[index]?.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER)) {
-        logger.warn(`${this.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
+        getLogger().warn(`${this.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
       } else if (phase && sampledValueTemplates[index]?.phase === phase && sampledValueTemplates[index]?.measurand === measurand
         && this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(measurand)) {
         return sampledValueTemplates[index];
@@ -260,10 +288,10 @@ export default class ChargingStation {
     }
     if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
       const errorMsg = `${this.logPrefix()} Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
-      logger.error(errorMsg);
+      getLogger().error(errorMsg);
       throw new Error(errorMsg);
     }
-    logger.debug(`${this.logPrefix()} No MeterValues for measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
+    getLogger().debug(`${this.logPrefix()} No MeterValues for measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
   }
 
   public getAutomaticTransactionGeneratorRequireAuthorize(): boolean {
@@ -276,11 +304,11 @@ export default class ChargingStation {
       this.heartbeatSetInterval = setInterval(async (): Promise<void> => {
         await this.ocppRequestService.sendHeartbeat();
       }, this.getHeartbeatInterval());
-      logger.info(this.logPrefix() + ' Heartbeat started every ' + Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()));
+      getLogger().info(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()));
+      getLogger().info(this.logPrefix() + ' Heartbeat already started every ' + Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()));
     } else {
-      logger.error(`${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval() ? Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()) : this.getHeartbeatInterval()}, not starting the heartbeat`);
+      getLogger().error(`${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval() ? Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()) : this.getHeartbeatInterval()}, not starting the heartbeat`);
     }
   }
 
@@ -293,18 +321,18 @@ export default class ChargingStation {
 
   public startMeterValues(connectorId: number, interval: number): void {
     if (connectorId === 0) {
-      logger.error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId.toString()}`);
+      getLogger().error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId.toString()}`);
       return;
     }
     if (!this.getConnectorStatus(connectorId)) {
-      logger.error(`${this.logPrefix()} Trying to start MeterValues on non existing connector Id ${connectorId.toString()}`);
+      getLogger().error(`${this.logPrefix()} Trying to start MeterValues on non existing connector Id ${connectorId.toString()}`);
       return;
     }
     if (!this.getConnectorStatus(connectorId)?.transactionStarted) {
-      logger.error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`);
+      getLogger().error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`);
       return;
     } else if (this.getConnectorStatus(connectorId)?.transactionStarted && !this.getConnectorStatus(connectorId)?.transactionId) {
-      logger.error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction id`);
+      getLogger().error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction id`);
       return;
     }
     if (interval > 0) {
@@ -313,7 +341,7 @@ export default class ChargingStation {
         await this.ocppRequestService.sendMeterValues(connectorId, this.getConnectorStatus(connectorId).transactionId, interval);
       }, interval);
     } else {
-      logger.error(`${this.logPrefix()} Charging station ${StandardParametersKey.MeterValueSampleInterval} configuration set to ${interval ? Utils.formatDurationMilliSeconds(interval) : interval}, not sending MeterValues`);
+      getLogger().error(`${this.logPrefix()} Charging station ${StandardParametersKey.MeterValueSampleInterval} configuration set to ${interval ? Utils.formatDurationMilliSeconds(interval) : interval}, not sending MeterValues`);
     }
   }
 
@@ -384,7 +412,7 @@ export default class ChargingStation {
         reboot,
       });
     } else {
-      logger.error(`${this.logPrefix()} Trying to add an already existing configuration key: %j`, keyFound);
+      getLogger().error(`${this.logPrefix()} Trying to add an already existing configuration key: %j`, keyFound);
     }
   }
 
@@ -394,7 +422,7 @@ export default class ChargingStation {
       const keyIndex = this.configuration.configurationKey.indexOf(keyFound);
       this.configuration.configurationKey[keyIndex].value = value;
     } else {
-      logger.error(`${this.logPrefix()} Trying to set a value on a non existing configuration key: %j`, { key, value });
+      getLogger().error(`${this.logPrefix()} Trying to set a value on a non existing configuration key: %j`, { key, value });
     }
   }
 
@@ -461,6 +489,10 @@ export default class ChargingStation {
     } catch (error) {
       FileUtils.handleFileException(this.logPrefix(), 'Template', this.stationTemplateFile, error as NodeJS.ErrnoException);
     }
+    const chargingStationId = this.getChargingStationId(stationTemplateFromFile);
+    // Deprecation template keys section
+    this.warnDeprecatedTemplateKey(stationTemplateFromFile, 'supervisionUrl', chargingStationId, 'Use \'supervisionUrls\' instead');
+    this.convertDeprecatedTemplateKey(stationTemplateFromFile, 'supervisionUrl', 'supervisionUrls');
     const stationInfo: ChargingStationInfo = stationTemplateFromFile ?? {} as ChargingStationInfo;
     stationInfo.wsOptions = stationTemplateFromFile?.wsOptions ?? {};
     if (!Utils.isEmptyArray(stationTemplateFromFile.power)) {
@@ -477,7 +509,7 @@ export default class ChargingStation {
     }
     delete stationInfo.power;
     delete stationInfo.powerUnit;
-    stationInfo.chargingStationId = this.getChargingStationId(stationTemplateFromFile);
+    stationInfo.chargingStationId = chargingStationId;
     stationInfo.resetTime = stationTemplateFromFile.resetTime ? stationTemplateFromFile.resetTime * 1000 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
     return stationInfo;
   }
@@ -488,7 +520,7 @@ export default class ChargingStation {
 
   private handleUnsupportedVersion(version: OCPPVersion) {
     const errMsg = `${this.logPrefix()} Unsupported protocol version '${version}' configured in template file ${this.stationTemplateFile}`;
-    logger.error(errMsg);
+    getLogger().error(errMsg);
     throw new Error(errMsg);
   }
 
@@ -505,18 +537,18 @@ export default class ChargingStation {
     // Build connectors if needed
     const maxConnectors = this.getMaxNumberOfConnectors();
     if (maxConnectors <= 0) {
-      logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with ${maxConnectors} connectors`);
+      getLogger().warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with ${maxConnectors} connectors`);
     }
     const templateMaxConnectors = this.getTemplateMaxNumberOfConnectors();
     if (templateMaxConnectors <= 0) {
-      logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector configuration`);
+      getLogger().warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector configuration`);
     }
     if (!this.stationInfo.Connectors[0]) {
-      logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector Id 0 configuration`);
+      getLogger().warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector Id 0 configuration`);
     }
     // Sanity check
     if (maxConnectors > (this.stationInfo.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) && !this.stationInfo.randomConnectors) {
-      logger.warn(`${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${this.stationTemplateFile}, forcing random connector configurations affectation`);
+      getLogger().warn(`${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${this.stationTemplateFile}, forcing random connector configurations affectation`);
       this.stationInfo.randomConnectors = true;
     }
     const connectorsConfigHash = crypto.createHash('sha256').update(JSON.stringify(this.stationInfo.Connectors) + maxConnectors.toString()).digest('hex');
@@ -622,31 +654,27 @@ export default class ChargingStation {
   }
 
   private async onOpen(): Promise<void> {
-    logger.info(`${this.logPrefix()} Connected to OCPP server through ${this.wsConnectionUrl.toString()}`);
-    if (!this.isRegistered()) {
+    getLogger().info(`${this.logPrefix()} Connected to OCPP server through ${this.wsConnectionUrl.toString()}`);
+    if (!this.isInAcceptedState()) {
       // Send BootNotification
       let registrationRetryCount = 0;
       do {
         this.bootNotificationResponse = await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
           this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
-        if (!this.isRegistered()) {
-          registrationRetryCount++;
+        if (!this.isInAcceptedState()) {
+          this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
           await Utils.sleep(this.bootNotificationResponse?.interval ? this.bootNotificationResponse.interval * 1000 : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL);
         }
-      } while (!this.isRegistered() && (registrationRetryCount <= this.getRegistrationMaxRetries() || this.getRegistrationMaxRetries() === -1));
+      } while (!this.isInAcceptedState() && (registrationRetryCount <= this.getRegistrationMaxRetries() || this.getRegistrationMaxRetries() === -1));
     }
-    if (this.isRegistered() && this.stationInfo.autoRegister) {
-      await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
-        this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
-    }
-    if (this.isRegistered()) {
+    if (this.isInAcceptedState()) {
       await this.startMessageSequence();
       this.stopped && (this.stopped = false);
       if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) {
         this.flushMessageBuffer();
       }
     } else {
-      logger.error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`);
+      getLogger().error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`);
     }
     this.autoReconnectRetryCount = 0;
     this.wsConnectionRestarted = false;
@@ -657,12 +685,12 @@ export default class ChargingStation {
       // Normal close
       case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
       case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
-        logger.info(`${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(code)}' and reason '${reason}'`);
+        getLogger().info(`${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(code)}' and reason '${reason}'`);
         this.autoReconnectRetryCount = 0;
         break;
       // Abnormal close
       default:
-        logger.error(`${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(code)}' and reason '${reason}'`);
+        getLogger().error(`${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(code)}' and reason '${reason}'`);
         await this.reconnect(code);
         break;
     }
@@ -670,10 +698,10 @@ export default class ChargingStation {
 
   private async onMessage(data: Data): Promise<void> {
     let [messageType, messageId, commandName, commandPayload, errorDetails]: IncomingRequest = [0, '', '' as IncomingRequestCommand, {}, {}];
-    let responseCallback: (payload: Record<string, unknown> | string, requestPayload: Record<string, unknown>) => void;
+    let responseCallback: (payload: JsonType | string, requestPayload: JsonType | OCPPError) => void;
     let rejectCallback: (error: OCPPError, requestStatistic?: boolean) => void;
     let requestCommandName: RequestCommand | IncomingRequestCommand;
-    let requestPayload: Record<string, unknown>;
+    let requestPayload: JsonType | OCPPError;
     let cachedRequest: CachedRequest;
     let errMsg: string;
     try {
@@ -726,27 +754,27 @@ export default class ChargingStation {
         // Error
         default:
           errMsg = `${this.logPrefix()} Wrong message type ${messageType}`;
-          logger.error(errMsg);
+          getLogger().error(errMsg);
           throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
       }
     } catch (error) {
       // Log
-      logger.error('%s Incoming OCPP message %j matching cached request %j processing error %j', this.logPrefix(), data.toString(), this.requests.get(messageId), error);
+      getLogger().error('%s Incoming OCPP message %j matching cached request %j processing error %j', this.logPrefix(), data.toString(), this.requests.get(messageId), error);
       // Send error
       messageType === MessageType.CALL_MESSAGE && await this.ocppRequestService.sendError(messageId, error as OCPPError, commandName);
     }
   }
 
   private onPing(): void {
-    logger.debug(this.logPrefix() + ' Received a WS ping (rfc6455) from the server');
+    getLogger().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');
+    getLogger().debug(this.logPrefix() + ' Received a WS pong (rfc6455) from the server');
   }
 
   private async onError(error: WSError): Promise<void> {
-    logger.error(this.logPrefix() + ' WebSocket error: %j', error);
+    getLogger().error(this.logPrefix() + ' WebSocket error: %j', error);
     // switch (error.code) {
     //   case 'ECONNREFUSED':
     //     await this.reconnect(error);
@@ -775,7 +803,7 @@ export default class ChargingStation {
         FileUtils.handleFileException(this.logPrefix(), 'Authorization', authorizationFile, error as NodeJS.ErrnoException);
       }
     } else {
-      logger.info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile);
+      getLogger().info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile);
     }
     return authorizedTags;
   }
@@ -848,6 +876,10 @@ export default class ChargingStation {
   }
 
   private async startMessageSequence(): Promise<void> {
+    if (this.stationInfo.autoRegister) {
+      await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
+        this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
+    }
     // Start WebSocket ping
     this.startWebSocketPing();
     // Start heartbeat
@@ -919,11 +951,11 @@ export default class ChargingStation {
           this.wsConnection.ping((): void => { /* This is intentional */ });
         }
       }, webSocketPingInterval * 1000);
-      logger.info(this.logPrefix() + ' WebSocket ping started every ' + Utils.formatDurationSeconds(webSocketPingInterval));
+      getLogger().info(this.logPrefix() + ' WebSocket ping started every ' + Utils.formatDurationSeconds(webSocketPingInterval));
     } else if (this.webSocketPingSetInterval) {
-      logger.info(this.logPrefix() + ' WebSocket ping every ' + Utils.formatDurationSeconds(webSocketPingInterval) + ' already started');
+      getLogger().info(this.logPrefix() + ' WebSocket ping every ' + Utils.formatDurationSeconds(webSocketPingInterval) + ' already started');
     } else {
-      logger.error(`${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval ? Utils.formatDurationSeconds(webSocketPingInterval) : webSocketPingInterval}, not starting the WebSocket ping`);
+      getLogger().error(`${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval ? Utils.formatDurationSeconds(webSocketPingInterval) : webSocketPingInterval}, not starting the WebSocket ping`);
     }
   }
 
@@ -933,17 +965,45 @@ export default class ChargingStation {
     }
   }
 
+  private warnDeprecatedTemplateKey(template: ChargingStationTemplate, key: string, chargingStationId: string, logMsgToAppend = ''): void {
+    if (!Utils.isUndefined(template[key])) {
+      getLogger().warn(`${Utils.logPrefix(` ${chargingStationId} |`)} Deprecated template key '${key}' usage in file '${this.stationTemplateFile}'${logMsgToAppend && '. ' + logMsgToAppend}`);
+    }
+  }
+
+  private convertDeprecatedTemplateKey(template: ChargingStationTemplate, deprecatedKey: string, key: string): void {
+    if (!Utils.isUndefined(template[deprecatedKey])) {
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+      template[key] = template[deprecatedKey];
+      delete template[deprecatedKey];
+    }
+  }
+
   private getConfiguredSupervisionUrl(): URL {
-    const supervisionUrls = Utils.cloneObject<string | string[]>(this.stationInfo.supervisionUrl ?? Configuration.getSupervisionUrls());
-    let indexUrl = 0;
+    const supervisionUrls = Utils.cloneObject<string | string[]>(this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls());
     if (!Utils.isEmptyArray(supervisionUrls)) {
-      if (Configuration.getDistributeStationsToTenantsEqually()) {
-        indexUrl = this.index % supervisionUrls.length;
-      } else {
-        // Get a random url
-        indexUrl = Math.floor(Utils.secureRandom() * supervisionUrls.length);
+      let urlIndex = 0;
+      switch (Configuration.getSupervisionUrlDistribution()) {
+        case SupervisionUrlDistribution.ROUND_ROBIN:
+          urlIndex = (this.index - 1) % supervisionUrls.length;
+          break;
+        case SupervisionUrlDistribution.RANDOM:
+          // Get a random url
+          urlIndex = Math.floor(Utils.secureRandom() * supervisionUrls.length);
+          break;
+        case SupervisionUrlDistribution.SEQUENTIAL:
+          if (this.index <= supervisionUrls.length) {
+            urlIndex = this.index - 1;
+          } else {
+            getLogger().warn(`${this.logPrefix()} No more configured supervision urls available, using the first one`);
+          }
+          break;
+        default:
+          getLogger().error(`${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${SupervisionUrlDistribution.ROUND_ROBIN}`);
+          urlIndex = (this.index - 1) % supervisionUrls.length;
+          break;
       }
-      return new URL(supervisionUrls[indexUrl]);
+      return new URL(supervisionUrls[urlIndex]);
     }
     return new URL(supervisionUrls as string);
   }
@@ -957,7 +1017,7 @@ export default class ChargingStation {
     if (HeartBeatInterval) {
       return Utils.convertToInt(HeartBeatInterval.value) * 1000;
     }
-    !this.stationInfo.autoRegister && logger.warn(`${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${Constants.DEFAULT_HEARTBEAT_INTERVAL}`);
+    !this.stationInfo.autoRegister && getLogger().warn(`${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${Constants.DEFAULT_HEARTBEAT_INTERVAL}`);
     return Constants.DEFAULT_HEARTBEAT_INTERVAL;
   }
 
@@ -985,7 +1045,7 @@ export default class ChargingStation {
         break;
     }
     this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
-    logger.info(this.logPrefix() + ' Open OCPP connection to URL ' + this.wsConnectionUrl.toString());
+    getLogger().info(this.logPrefix() + ' Open OCPP connection to URL ' + this.wsConnectionUrl.toString());
   }
 
   private stopMeterValues(connectorId: number) {
@@ -1001,11 +1061,11 @@ export default class ChargingStation {
         fs.watch(authorizationFile, (event, filename) => {
           if (filename && event === 'change') {
             try {
-              logger.debug(this.logPrefix() + ' Authorization file ' + authorizationFile + ' have changed, reload');
+              getLogger().debug(this.logPrefix() + ' Authorization file ' + authorizationFile + ' have changed, reload');
               // Initialize authorizedTags
               this.authorizedTags = this.getAuthorizedTags();
             } catch (error) {
-              logger.error(this.logPrefix() + ' Authorization file monitoring error: %j', error);
+              getLogger().error(this.logPrefix() + ' Authorization file monitoring error: %j', error);
             }
           }
         });
@@ -1013,7 +1073,7 @@ export default class ChargingStation {
         FileUtils.handleFileException(this.logPrefix(), 'Authorization', authorizationFile, error as NodeJS.ErrnoException);
       }
     } else {
-      logger.info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile + '. Not monitoring changes');
+      getLogger().info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile + '. Not monitoring changes');
     }
   }
 
@@ -1022,7 +1082,7 @@ export default class ChargingStation {
       fs.watch(this.stationTemplateFile, (event, filename): void => {
         if (filename && event === 'change') {
           try {
-            logger.debug(this.logPrefix() + ' Template file ' + this.stationTemplateFile + ' have changed, reload');
+            getLogger().debug(this.logPrefix() + ' Template file ' + this.stationTemplateFile + ' have changed, reload');
             // Initialize
             this.initialize();
             // Restart the ATG
@@ -1038,7 +1098,7 @@ export default class ChargingStation {
             }
             // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
           } catch (error) {
-            logger.error(this.logPrefix() + ' Charging station template file monitoring error: %j', error);
+            getLogger().error(this.logPrefix() + ' Charging station template file monitoring error: %j', error);
           }
         }
       });
@@ -1067,13 +1127,13 @@ export default class ChargingStation {
       this.autoReconnectRetryCount++;
       const reconnectDelay = (this.getReconnectExponentialDelay() ? Utils.exponentialDelay(this.autoReconnectRetryCount) : this.getConnectionTimeout() * 1000);
       const reconnectTimeout = (reconnectDelay - 100) > 0 && reconnectDelay;
-      logger.error(`${this.logPrefix()} WebSocket: connection retry in ${Utils.roundTo(reconnectDelay, 2)}ms, timeout ${reconnectTimeout}ms`);
+      getLogger().error(`${this.logPrefix()} WebSocket: connection retry in ${Utils.roundTo(reconnectDelay, 2)}ms, timeout ${reconnectTimeout}ms`);
       await Utils.sleep(reconnectDelay);
-      logger.error(this.logPrefix() + ' WebSocket: reconnecting try #' + this.autoReconnectRetryCount.toString());
+      getLogger().error(this.logPrefix() + ' WebSocket: reconnecting try #' + this.autoReconnectRetryCount.toString());
       this.openWSConnection({ ...this.stationInfo.wsOptions, handshakeTimeout: reconnectTimeout }, true);
       this.wsConnectionRestarted = true;
     } else if (this.getAutoReconnectMaxRetries() !== -1) {
-      logger.error(`${this.logPrefix()} WebSocket reconnect failure: max retries reached (${this.autoReconnectRetryCount}) or retry disabled (${this.getAutoReconnectMaxRetries()})`);
+      getLogger().error(`${this.logPrefix()} WebSocket reconnect failure: max retries reached (${this.autoReconnectRetryCount}) or retry disabled (${this.getAutoReconnectMaxRetries()})`);
     }
   }