fix: move and fix statistic related helpers implementation
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 2456d0d6965175a2a1ce366e4cbb7b25a6c90e28..9f1fa7dea8b29435e63592b2645406613d7a1526 100644 (file)
@@ -42,6 +42,7 @@ import {
   type BootNotificationRequest,
   type BootNotificationResponse,
   type CachedRequest,
+  type ChargingStationAutomaticTransactionGeneratorConfiguration,
   type ChargingStationConfiguration,
   type ChargingStationInfo,
   type ChargingStationOcppConfiguration,
@@ -90,6 +91,8 @@ import {
 } from '../types';
 import {
   ACElectricUtils,
+  AsyncLock,
+  AsyncLockType,
   Configuration,
   Constants,
   DCElectricUtils,
@@ -108,7 +111,7 @@ export class ChargingStation {
   public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
   public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
   public wsConnection!: WebSocket | null;
-  public connectors: Map<number, ConnectorStatus>;
+  public readonly connectors: Map<number, ConnectorStatus>;
   public readonly evses: Map<number, EvseStatus>;
   public readonly requests: Map<string, CachedRequest>;
   public performanceStatistics!: PerformanceStatistics | undefined;
@@ -182,8 +185,9 @@ export class ChargingStation {
   };
 
   public hasIdTags(): boolean {
-    const idTagsFile = ChargingStationUtils.getIdTagsFile(this.stationInfo);
-    return Utils.isNotEmptyArray(this.idTagsCache.getIdTags(idTagsFile));
+    return Utils.isNotEmptyArray(
+      this.idTagsCache.getIdTags(ChargingStationUtils.getIdTagsFile(this.stationInfo))
+    );
   }
 
   public getEnableStatistics(): boolean {
@@ -316,10 +320,7 @@ export class ChargingStation {
       this.logPrefix(),
       this.templateFile
     );
-    const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
-    return !Utils.isUndefined(localStationInfo.voltageOut)
-      ? localStationInfo.voltageOut
-      : defaultVoltageOut;
+    return (stationInfo ?? this.stationInfo).voltageOut ?? defaultVoltageOut;
   }
 
   public getMaximumPower(stationInfo?: ChargingStationInfo): number {
@@ -658,9 +659,7 @@ export class ChargingStation {
                 this.initialize();
                 // Restart the ATG
                 this.stopAutomaticTransactionGenerator();
-                if (
-                  this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true
-                ) {
+                if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
                   this.startAutomaticTransactionGenerator();
                 }
                 if (this.getEnableStatistics() === true) {
@@ -703,6 +702,7 @@ export class ChargingStation {
         this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
         delete this.bootNotificationResponse;
         this.started = false;
+        this.saveConfiguration();
         parentPort?.postMessage(MessageChannelUtils.buildStoppedMessage(this));
         this.stopping = false;
       } else {
@@ -812,15 +812,19 @@ export class ChargingStation {
     }
   }
 
-  public startAutomaticTransactionGenerator(
-    connectorIds?: number[],
-    automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration
-  ): void {
-    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(
-      automaticTransactionGeneratorConfiguration ??
-        this.getAutomaticTransactionGeneratorConfigurationFromTemplate(),
-      this
-    );
+  public getAutomaticTransactionGeneratorConfiguration():
+    | AutomaticTransactionGeneratorConfiguration
+    | undefined {
+    const automaticTransactionGeneratorConfigurationFromFile =
+      this.getConfigurationFromFile()?.automaticTransactionGenerator;
+    if (automaticTransactionGeneratorConfigurationFromFile) {
+      return automaticTransactionGeneratorConfigurationFromFile;
+    }
+    return this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+  }
+
+  public startAutomaticTransactionGenerator(connectorIds?: number[]): void {
+    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this);
     if (Utils.isNotEmptyArray(connectorIds)) {
       for (const connectorId of connectorIds) {
         this.automaticTransactionGenerator?.startConnector(connectorId);
@@ -828,6 +832,7 @@ export class ChargingStation {
     } else {
       this.automaticTransactionGenerator?.start();
     }
+    this.saveChargingStationAutomaticTransactionGeneratorConfiguration();
     parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
   }
 
@@ -839,6 +844,7 @@ export class ChargingStation {
     } else {
       this.automaticTransactionGenerator?.stop();
     }
+    this.saveChargingStationAutomaticTransactionGeneratorConfiguration();
     parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
   }
 
@@ -1309,18 +1315,15 @@ export class ChargingStation {
 
   private initializeConnectorsOrEvsesFromFile(configuration: ChargingStationConfiguration): void {
     if (configuration?.connectorsStatus && !configuration?.evsesStatus) {
-      this.connectors = new Map<number, ConnectorStatus>(
-        configuration?.connectorsStatus.map((connectorStatus, connectorId) => [
-          connectorId,
-          connectorStatus,
-        ])
-      );
+      for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) {
+        this.connectors.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
+      }
     } else if (configuration?.evsesStatus && !configuration?.connectorsStatus) {
       for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) {
-        const evseStatus = Utils.cloneObject(evseStatusConfiguration);
+        const evseStatus = Utils.cloneObject<EvseStatusConfiguration>(evseStatusConfiguration);
         delete evseStatus.connectorsStatus;
         this.evses.set(evseId, {
-          ...evseStatus,
+          ...(evseStatus as EvseStatus),
           connectors: new Map<number, ConnectorStatus>(
             evseStatusConfiguration.connectorsStatus.map((connectorStatus, connectorId) => [
               connectorId,
@@ -1523,6 +1526,20 @@ export class ChargingStation {
     return configuration;
   }
 
+  private saveChargingStationAutomaticTransactionGeneratorConfiguration(
+    stationTemplate?: ChargingStationTemplate
+  ): void {
+    this.saveConfiguration({
+      automaticTransactionGenerator: (stationTemplate ?? this.getTemplateFromFile())
+        .AutomaticTransactionGenerator,
+      ...(!Utils.isNullOrUndefined(this.automaticTransactionGenerator?.connectorsStatus) && {
+        automaticTransactionGeneratorStatuses: [
+          ...this.automaticTransactionGenerator.connectorsStatus.values(),
+        ],
+      }),
+    });
+  }
+
   private saveConnectorsStatus() {
     this.saveConfiguration();
   }
@@ -1531,20 +1548,28 @@ export class ChargingStation {
     this.saveConfiguration();
   }
 
-  private saveConfiguration(): void {
+  private saveConfiguration(
+    chargingStationAutomaticTransactionGeneratorConfiguration?: ChargingStationAutomaticTransactionGeneratorConfiguration
+  ): void {
     if (this.configurationFile) {
       try {
         if (!fs.existsSync(path.dirname(this.configurationFile))) {
           fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
         }
-        const configurationData: ChargingStationConfiguration =
-          Utils.cloneObject(this.getConfigurationFromFile()) ?? {};
+        let configurationData: ChargingStationConfiguration =
+          Utils.cloneObject<ChargingStationConfiguration>(this.getConfigurationFromFile()) ?? {};
         if (this.getStationInfoPersistentConfiguration() && this.stationInfo) {
           configurationData.stationInfo = this.stationInfo;
         }
         if (this.getOcppPersistentConfiguration() && this.ocppConfiguration?.configurationKey) {
           configurationData.configurationKey = this.ocppConfiguration.configurationKey;
         }
+        if (chargingStationAutomaticTransactionGeneratorConfiguration) {
+          configurationData = merge<ChargingStationConfiguration>(
+            configurationData,
+            chargingStationAutomaticTransactionGeneratorConfiguration
+          );
+        }
         if (this.connectors.size > 0) {
           configurationData.connectorsStatus = [...this.connectors.values()].map(
             // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -1570,16 +1595,30 @@ export class ChargingStation {
           .update(JSON.stringify(configurationData))
           .digest('hex');
         if (this.configurationFileHash !== configurationHash) {
-          configurationData.configurationHash = configurationHash;
-          const measureId = `${FileType.ChargingStationConfiguration} write`;
-          const beginId = PerformanceStatistics.beginMeasure(measureId);
-          const fileDescriptor = fs.openSync(this.configurationFile, 'w');
-          fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
-          fs.closeSync(fileDescriptor);
-          PerformanceStatistics.endMeasure(measureId, beginId);
-          this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
-          this.sharedLRUCache.setChargingStationConfiguration(configurationData);
-          this.configurationFileHash = configurationHash;
+          AsyncLock.acquire(AsyncLockType.configuration)
+            .then(() => {
+              configurationData.configurationHash = configurationHash;
+              const measureId = `${FileType.ChargingStationConfiguration} write`;
+              const beginId = PerformanceStatistics.beginMeasure(measureId);
+              const fileDescriptor = fs.openSync(this.configurationFile, 'w');
+              fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
+              fs.closeSync(fileDescriptor);
+              PerformanceStatistics.endMeasure(measureId, beginId);
+              this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
+              this.sharedLRUCache.setChargingStationConfiguration(configurationData);
+              this.configurationFileHash = configurationHash;
+            })
+            .catch((error) => {
+              FileUtils.handleFileException(
+                this.configurationFile,
+                FileType.ChargingStationConfiguration,
+                error as NodeJS.ErrnoException,
+                this.logPrefix()
+              );
+            })
+            .finally(() => {
+              AsyncLock.release(AsyncLockType.configuration).catch(Constants.EMPTY_FUNCTION);
+            });
         } else {
           logger.debug(
             `${this.logPrefix()} Not saving unchanged charging station configuration file ${
@@ -1607,18 +1646,9 @@ export class ChargingStation {
   }
 
   private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined {
-    let configuration: ChargingStationConfiguration | undefined;
     if (this.getOcppPersistentConfiguration() === true) {
-      const configurationFromFile = this.getConfigurationFromFile();
-      configuration = configurationFromFile?.configurationKey && configurationFromFile;
+      return { configurationKey: this.getConfigurationFromFile()?.configurationKey };
     }
-    if (!Utils.isNullOrUndefined(configuration)) {
-      delete configuration.stationInfo;
-      delete configuration.connectorsStatus;
-      delete configuration.evsesStatus;
-      delete configuration.configurationHash;
-    }
-    return configuration;
   }
 
   private getOcppConfiguration(): ChargingStationOcppConfiguration | undefined {
@@ -2009,7 +2039,8 @@ export class ChargingStation {
             await OCPPServiceUtils.sendAndSetConnectorStatus(
               this,
               connectorId,
-              connectorBootStatus
+              connectorBootStatus,
+              evseId
             );
           }
         }
@@ -2037,7 +2068,7 @@ export class ChargingStation {
     }
 
     // Start the ATG
-    if (this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true) {
+    if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
       this.startAutomaticTransactionGenerator();
     }
     this.wsConnectionRestarted === true && this.flushMessageBuffer();
@@ -2069,7 +2100,8 @@ export class ChargingStation {
               OCPPServiceUtils.buildStatusNotificationRequest(
                 this,
                 connectorId,
-                ConnectorStatusEnum.Unavailable
+                ConnectorStatusEnum.Unavailable,
+                evseId
               )
             );
             delete connectorStatus?.status;
@@ -2191,7 +2223,7 @@ export class ChargingStation {
     // Stop heartbeat
     this.stopHeartbeat();
     // Stop the ATG if needed
-    if (this.automaticTransactionGenerator?.configuration?.stopOnConnectionFailure === true) {
+    if (this.getAutomaticTransactionGeneratorConfiguration().stopOnConnectionFailure === true) {
       this.stopAutomaticTransactionGenerator();
     }
     if (
@@ -2233,10 +2265,4 @@ export class ChargingStation {
       );
     }
   }
-
-  private getAutomaticTransactionGeneratorConfigurationFromTemplate():
-    | AutomaticTransactionGeneratorConfiguration
-    | undefined {
-    return this.getTemplateFromFile()?.AutomaticTransactionGenerator;
-  }
 }