Unify supervision urls setup and distribution to charging stations
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index c50d2ad9ba3bf104f14b64aa4a7770698d95d785..4f6c7771283f865bb44d700541fb5fe42e0fbf36 100644 (file)
@@ -5,7 +5,6 @@ import { BootNotificationResponse, RegistrationStatus } from '../types/ocpp/Resp
 import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration';
 import ChargingStationTemplate, { CurrentType, PowerUnits, Voltage } from '../types/ChargingStationTemplate';
 import { ConnectorPhaseRotation, StandardParametersKey, SupportedFeatureProfiles, VendorDefaultParametersKey } from '../types/ocpp/Configuration';
-import { ConnectorStatus, SampledValueTemplate } from '../types/Connectors';
 import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues';
 import { WSError, WebSocketCloseEventStatusCode } from '../types/WebSocket';
 import WebSocket, { ClientOptions, Data, OPEN } from 'ws';
@@ -17,6 +16,7 @@ import ChargingStationInfo from '../types/ChargingStationInfo';
 import { ChargingStationWorkerMessageEvents } from '../types/ChargingStationWorker';
 import { ClientRequestArgs } from 'http';
 import Configuration from '../utils/Configuration';
+import { ConnectorStatus } from '../types/ConnectorStatus';
 import Constants from '../utils/Constants';
 import { ErrorType } from '../types/ocpp/ErrorType';
 import FileUtils from '../utils/FileUtils';
@@ -24,12 +24,14 @@ import { MessageType } from '../types/ocpp/MessageType';
 import OCPP16IncomingRequestService from './ocpp/1.6/OCPP16IncomingRequestService';
 import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService';
 import OCPP16ResponseService from './ocpp/1.6/OCPP16ResponseService';
-import OCPPError from './ocpp/OCPPError';
+import OCPPError from '../exception/OCPPError';
 import OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
 import OCPPRequestService from './ocpp/OCPPRequestService';
 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';
@@ -79,7 +81,7 @@ export default class ChargingStation {
   }
 
   get wsConnectionUrl(): URL {
-    return this.getSupervisionURLOCPPConfiguration() ? new URL(this.getConfigurationKey(this.stationInfo.supervisionURLOCPPKey ?? VendorDefaultParametersKey.ConnectionUrl).value + '/' + this.stationInfo.chargingStationId) : this.wsConfiguredConnectionUrl;
+    return this.getSupervisionUrlOcppConfiguration() ? new URL(this.getConfigurationKey(this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl).value + '/' + this.stationInfo.chargingStationId) : this.wsConfiguredConnectionUrl;
   }
 
   public logPrefix(): string {
@@ -117,11 +119,11 @@ export default class ChargingStation {
   }
 
   public isWebSocketConnectionOpened(): boolean {
-    return this.wsConnection?.readyState === OPEN;
+    return this?.wsConnection?.readyState === OPEN;
   }
 
   public isRegistered(): boolean {
-    return this.bootNotificationResponse?.status === RegistrationStatus.ACCEPTED;
+    return this?.bootNotificationResponse?.status === RegistrationStatus.ACCEPTED;
   }
 
   public isChargingStationAvailable(): boolean {
@@ -439,8 +441,8 @@ export default class ChargingStation {
     }
   }
 
-  private getSupervisionURLOCPPConfiguration(): boolean {
-    return this.stationInfo.supervisionURLOCPPConfiguration ?? false;
+  private getSupervisionUrlOcppConfiguration(): boolean {
+    return this.stationInfo.supervisionUrlOcppConfiguration ?? false;
   }
 
   private getChargingStationId(stationTemplate: ChargingStationTemplate): string {
@@ -460,6 +462,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)) {
@@ -476,12 +482,12 @@ 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;
   }
 
-  private getOCPPVersion(): OCPPVersion {
+  private getOcppVersion(): OCPPVersion {
     return this.stationInfo.ocppVersion ? this.stationInfo.ocppVersion : OCPPVersion.VERSION_16;
   }
 
@@ -555,18 +561,18 @@ export default class ChargingStation {
         this.initializeConnectorStatus(connectorId);
       }
     }
-    this.wsConfiguredConnectionUrl = new URL(this.getConfiguredSupervisionURL().href + '/' + this.stationInfo.chargingStationId);
-    switch (this.getOCPPVersion()) {
+    this.wsConfiguredConnectionUrl = new URL(this.getConfiguredSupervisionUrl().href + '/' + this.stationInfo.chargingStationId);
+    switch (this.getOcppVersion()) {
       case OCPPVersion.VERSION_16:
         this.ocppIncomingRequestService = new OCPP16IncomingRequestService(this);
         this.ocppRequestService = new OCPP16RequestService(this, new OCPP16ResponseService(this));
         break;
       default:
-        this.handleUnsupportedVersion(this.getOCPPVersion());
+        this.handleUnsupportedVersion(this.getOcppVersion());
         break;
     }
     // OCPP parameters
-    this.initOCPPParameters();
+    this.initOcppParameters();
     if (this.stationInfo.autoRegister) {
       this.bootNotificationResponse = {
         currentTime: new Date().toISOString(),
@@ -580,9 +586,9 @@ export default class ChargingStation {
     }
   }
 
-  private initOCPPParameters(): void {
-    if (this.getSupervisionURLOCPPConfiguration() && !this.getConfigurationKey(this.stationInfo.supervisionURLOCPPKey ?? VendorDefaultParametersKey.ConnectionUrl)) {
-      this.addConfigurationKey(VendorDefaultParametersKey.ConnectionUrl, this.getConfiguredSupervisionURL().href, { reboot: true });
+  private initOcppParameters(): void {
+    if (this.getSupervisionUrlOcppConfiguration() && !this.getConfigurationKey(this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl)) {
+      this.addConfigurationKey(VendorDefaultParametersKey.ConnectionUrl, this.getConfiguredSupervisionUrl().href, { reboot: true });
     }
     if (!this.getConfigurationKey(StandardParametersKey.SupportedFeatureProfiles)) {
       this.addConfigurationKey(StandardParametersKey.SupportedFeatureProfiles, `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.Local_Auth_List_Management},${SupportedFeatureProfiles.Smart_Charging}`);
@@ -634,6 +640,10 @@ export default class ChargingStation {
         }
       } while (!this.isRegistered() && (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()) {
       await this.startMessageSequence();
       this.stopped && (this.stopped = false);
@@ -829,7 +839,7 @@ export default class ChargingStation {
   }
 
   private getMaxNumberOfConnectors(): number {
-    let maxConnectors = 0;
+    let maxConnectors: number;
     if (!Utils.isEmptyArray(this.stationInfo.numberOfConnectors)) {
       const numberOfConnectors = this.stationInfo.numberOfConnectors as number[];
       // Distribute evenly the number of connectors
@@ -928,17 +938,45 @@ export default class ChargingStation {
     }
   }
 
-  private getConfiguredSupervisionURL(): URL {
-    const supervisionUrls = Utils.cloneObject<string | string[]>(this.stationInfo.supervisionURL ?? Configuration.getSupervisionURLs());
-    let indexUrl = 0;
+  private warnDeprecatedTemplateKey(template: ChargingStationTemplate, key: string, chargingStationId: string, logMsgToAppend = ''): void {
+    if (!Utils.isUndefined(template[key])) {
+      logger.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.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 {
+            logger.warn(`${this.logPrefix()} No more configured supervision urls available, using the first one`);
+          }
+          break;
+        default:
+          logger.error(`${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}', 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);
   }
@@ -971,12 +1009,12 @@ export default class ChargingStation {
       this.wsConnection.close();
     }
     let protocol: string;
-    switch (this.getOCPPVersion()) {
+    switch (this.getOcppVersion()) {
       case OCPPVersion.VERSION_16:
         protocol = 'ocpp' + OCPPVersion.VERSION_16;
         break;
       default:
-        this.handleUnsupportedVersion(this.getOCPPVersion());
+        this.handleUnsupportedVersion(this.getOcppVersion());
         break;
     }
     this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);