fix: fix return at getting connector status with evses
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index b3d4bdd98016cbf5d35288c15bff552417859497..b52fb585a63c226b5998ea444158d0496c24a0c2 100644 (file)
@@ -46,7 +46,6 @@ import {
   type ChargingStationInfo,
   type ChargingStationOcppConfiguration,
   type ChargingStationTemplate,
-  ConnectorPhaseRotation,
   type ConnectorStatus,
   ConnectorStatusEnum,
   CurrentType,
@@ -54,6 +53,7 @@ import {
   type ErrorResponse,
   ErrorType,
   type EvseStatus,
+  type EvseStatusConfiguration,
   FileType,
   FirmwareStatus,
   type FirmwareStatusNotificationRequest,
@@ -151,6 +151,10 @@ export class ChargingStation {
     this.initialize();
   }
 
+  public get hasEvses(): boolean {
+    return this.connectors.size === 0 && this.evses.size > 0;
+  }
+
   private get wsConnectionUrl(): URL {
     return new URL(
       `${
@@ -240,16 +244,52 @@ export class ChargingStation {
     return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative;
   }
 
-  public isConnectorAvailable(id: number): boolean {
-    return id > 0 && this.getConnectorStatus(id)?.availability === AvailabilityType.Operative;
+  public hasConnector(connectorId: number): boolean {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        if (evseStatus.connectors.has(connectorId)) {
+          return true;
+        }
+      }
+      return false;
+    }
+    return this.connectors.has(connectorId);
+  }
+
+  public isConnectorAvailable(connectorId: number): boolean {
+    return (
+      connectorId > 0 &&
+      this.getConnectorStatus(connectorId)?.availability === AvailabilityType.Operative
+    );
   }
 
   public getNumberOfConnectors(): number {
-    return this.connectors.get(0) ? this.connectors.size - 1 : this.connectors.size;
+    if (this.hasEvses) {
+      let numberOfConnectors = 0;
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          numberOfConnectors += evseStatus.connectors.size;
+        }
+      }
+      return numberOfConnectors;
+    }
+    return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size;
   }
 
-  public getConnectorStatus(id: number): ConnectorStatus | undefined {
-    return this.connectors.get(id);
+  public getNumberOfEvses(): number {
+    return this.evses.has(0) ? this.evses.size - 1 : this.evses.size;
+  }
+
+  public getConnectorStatus(connectorId: number): ConnectorStatus | undefined {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        if (evseStatus.connectors.has(connectorId)) {
+          return evseStatus.connectors.get(connectorId);
+        }
+      }
+      return undefined;
+    }
+    return this.connectors.get(connectorId);
   }
 
   public getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType {
@@ -306,12 +346,22 @@ export 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 (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.transactionId === transactionId) {
+            return connectorStatus.transactionIdTag;
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (
+          connectorId > 0 &&
+          this.getConnectorStatus(connectorId)?.transactionId === transactionId
+        ) {
+          return this.getConnectorStatus(connectorId)?.transactionIdTag;
+        }
       }
     }
   }
@@ -345,12 +395,22 @@ export class ChargingStation {
   }
 
   public getConnectorIdByTransactionId(transactionId: number): number | undefined {
-    for (const connectorId of this.connectors.keys()) {
-      if (
-        connectorId > 0 &&
-        this.getConnectorStatus(connectorId)?.transactionId === transactionId
-      ) {
-        return connectorId;
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (connectorStatus.transactionId === transactionId) {
+            return connectorId;
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (
+          connectorId > 0 &&
+          this.getConnectorStatus(connectorId)?.transactionId === transactionId
+        ) {
+          return connectorId;
+        }
       }
     }
   }
@@ -631,7 +691,7 @@ export class ChargingStation {
 
   public saveOcppConfiguration(): void {
     if (this.getOcppPersistentConfiguration()) {
-      this.saveConfiguration();
+      this.saveConfiguration({ stationInfo: false, connectors: false, evses: false });
     }
   }
 
@@ -952,7 +1012,7 @@ export class ChargingStation {
 
   private saveStationInfo(): void {
     if (this.getStationInfoPersistentConfiguration()) {
-      this.saveConfiguration();
+      this.saveConfiguration({ ocppConfiguration: false, connectors: false, evses: false });
     }
   }
 
@@ -1148,24 +1208,26 @@ export class ChargingStation {
         StandardParametersKey.ConnectorPhaseRotation
       )
     ) {
-      const connectorPhaseRotation = [];
-      for (const connectorId of this.connectors.keys()) {
-        // AC/DC
-        if (connectorId === 0 && this.getNumberOfPhases() === 0) {
-          connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
-        } else if (connectorId > 0 && this.getNumberOfPhases() === 0) {
-          connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
-          // AC
-        } else if (connectorId > 0 && this.getNumberOfPhases() === 1) {
-          connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
-        } else if (connectorId > 0 && this.getNumberOfPhases() === 3) {
-          connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
+      const connectorsPhaseRotation: string[] = [];
+      if (this.hasEvses) {
+        for (const evseStatus of this.evses.values()) {
+          for (const connectorId of evseStatus.connectors.keys()) {
+            connectorsPhaseRotation.push(
+              ChargingStationUtils.getPhaseRotationValue(connectorId, this.getNumberOfPhases())
+            );
+          }
+        }
+      } else {
+        for (const connectorId of this.connectors.keys()) {
+          connectorsPhaseRotation.push(
+            ChargingStationUtils.getPhaseRotationValue(connectorId, this.getNumberOfPhases())
+          );
         }
       }
       ChargingStationConfigurationUtils.addConfigurationKey(
         this,
         StandardParametersKey.ConnectorPhaseRotation,
-        connectorPhaseRotation.toString()
+        connectorsPhaseRotation.toString()
       );
     }
     if (
@@ -1257,22 +1319,6 @@ export class ChargingStation {
       if (this.connectors?.size === 0 || connectorsConfigChanged) {
         connectorsConfigChanged && this.connectors.clear();
         this.connectorsConfigurationHash = connectorsConfigHash;
-        const connectorZeroStatus = stationInfo?.Connectors[0];
-        // Add connector id 0
-        if (connectorZeroStatus && this.getUseConnectorId0(stationInfo) === true) {
-          ChargingStationUtils.checkStationInfoConnectorStatus(
-            0,
-            connectorZeroStatus,
-            this.logPrefix(),
-            this.templateFile
-          );
-          this.connectors.set(0, Utils.cloneObject<ConnectorStatus>(connectorZeroStatus));
-          this.getConnectorStatus(0).availability = AvailabilityType.Operative;
-          if (Utils.isUndefined(this.getConnectorStatus(0)?.chargingProfiles)) {
-            this.getConnectorStatus(0).chargingProfiles = [];
-          }
-        }
-        // Add remaining connectors
         const templateMaxConnectors = ChargingStationUtils.getMaxNumberOfConnectors(
           stationInfo.Connectors
         );
@@ -1281,9 +1327,11 @@ export class ChargingStation {
           this.templateFile,
           this.logPrefix()
         );
+        const templateMaxAvailableConnectors = stationInfo?.Connectors[0]
+          ? templateMaxConnectors - 1
+          : templateMaxConnectors;
         if (
-          configuredMaxConnectors >
-            (stationInfo?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) &&
+          configuredMaxConnectors > templateMaxAvailableConnectors &&
           !stationInfo?.randomConnectors
         ) {
           logger.warn(
@@ -1293,14 +1341,19 @@ export class ChargingStation {
           );
           stationInfo.randomConnectors = true;
         }
-        const templateMaxAvailableConnectors = stationInfo?.Connectors[0]
-          ? templateMaxConnectors - 1
-          : templateMaxConnectors;
-        if (templateMaxAvailableConnectors > 0) {
-          for (let connectorId = 1; connectorId <= configuredMaxConnectors; connectorId++) {
-            const templateConnectorId = stationInfo?.randomConnectors
-              ? Utils.getRandomInteger(templateMaxAvailableConnectors, 1)
-              : connectorId;
+        if (templateMaxConnectors > 0) {
+          for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
+            if (
+              connectorId === 0 &&
+              (!stationInfo?.Connectors[connectorId] ||
+                this.getUseConnectorId0(stationInfo) === false)
+            ) {
+              continue;
+            }
+            const templateConnectorId =
+              connectorId > 0 && stationInfo?.randomConnectors
+                ? Utils.getRandomInteger(templateMaxAvailableConnectors, 1)
+                : connectorId;
             const connectorStatus = stationInfo?.Connectors[templateConnectorId];
             ChargingStationUtils.checkStationInfoConnectorStatus(
               templateConnectorId,
@@ -1309,12 +1362,9 @@ export class ChargingStation {
               this.templateFile
             );
             this.connectors.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
-            this.getConnectorStatus(connectorId).availability = AvailabilityType.Operative;
-            if (Utils.isUndefined(this.getConnectorStatus(connectorId)?.chargingProfiles)) {
-              this.getConnectorStatus(connectorId).chargingProfiles = [];
-            }
-            ChargingStationUtils.initializeConnectorsMapStatus(this.connectors, this.logPrefix());
           }
+          ChargingStationUtils.initializeConnectorsMapStatus(this.connectors, this.logPrefix());
+          this.saveConnectorsStatus();
         } else {
           logger.warn(
             `${this.logPrefix()} Charging station information from template ${
@@ -1345,6 +1395,13 @@ export class ChargingStation {
         } with no evse id 0 configuration`
       );
     }
+    if (!stationInfo?.Evses[0]?.Connectors[0]) {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with evse id 0 with no connector id 0 configuration`
+      );
+    }
     if (stationInfo?.Evses) {
       const evsesConfigHash = crypto
         .createHash(Constants.DEFAULT_HASH_ALGORITHM)
@@ -1358,7 +1415,8 @@ export class ChargingStation {
         const templateMaxEvses = ChargingStationUtils.getMaxNumberOfEvses(stationInfo?.Evses);
         if (templateMaxEvses > 0) {
           for (const evse in stationInfo.Evses) {
-            this.evses.set(Utils.convertToInt(evse), {
+            const evseId = Utils.convertToInt(evse);
+            this.evses.set(evseId, {
               connectors: ChargingStationUtils.buildConnectorsMap(
                 stationInfo?.Evses[evse]?.Connectors,
                 this.logPrefix(),
@@ -1367,10 +1425,11 @@ export class ChargingStation {
               availability: AvailabilityType.Operative,
             });
             ChargingStationUtils.initializeConnectorsMapStatus(
-              this.evses.get(Utils.convertToInt(evse))?.connectors,
+              this.evses.get(evseId)?.connectors,
               this.logPrefix()
             );
           }
+          this.saveEvsesStatus();
         } else {
           logger.warn(
             `${this.logPrefix()} Charging station information from template ${
@@ -1378,13 +1437,13 @@ export class ChargingStation {
             } with no evses configuration defined, cannot create evses`
           );
         }
-      } else {
-        logger.warn(
-          `${this.logPrefix()} Charging station information from template ${
-            this.templateFile
-          } with no evses configuration defined, using already defined evses`
-        );
       }
+    } else {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with no evses configuration defined, using already defined evses`
+      );
     }
   }
 
@@ -1418,17 +1477,62 @@ export class ChargingStation {
     return configuration;
   }
 
-  private saveConfiguration(): void {
+  private saveConnectorsStatus() {
+    if (this.getOcppPersistentConfiguration()) {
+      this.saveConfiguration({ stationInfo: false, ocppConfiguration: false, evses: false });
+    }
+  }
+
+  private saveEvsesStatus() {
+    if (this.getOcppPersistentConfiguration()) {
+      this.saveConfiguration({ stationInfo: false, ocppConfiguration: false, connectors: false });
+    }
+  }
+
+  private saveConfiguration(
+    params: {
+      stationInfo?: boolean;
+      ocppConfiguration?: boolean;
+      connectors?: boolean;
+      evses?: boolean;
+    } = { stationInfo: true, ocppConfiguration: true, connectors: true, evses: true }
+  ): void {
     if (this.configurationFile) {
+      params = {
+        ...params,
+        ...{ stationInfo: true, ocppConfiguration: true, connectors: true, evses: true },
+      };
       try {
         if (!fs.existsSync(path.dirname(this.configurationFile))) {
           fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
         }
         const configurationData: ChargingStationConfiguration =
           Utils.cloneObject(this.getConfigurationFromFile()) ?? {};
-        this.ocppConfiguration?.configurationKey &&
-          (configurationData.configurationKey = this.ocppConfiguration.configurationKey);
-        this.stationInfo && (configurationData.stationInfo = this.stationInfo);
+        if (params.stationInfo && this.stationInfo) {
+          configurationData.stationInfo = this.stationInfo;
+        }
+        if (params.ocppConfiguration && this.ocppConfiguration?.configurationKey) {
+          configurationData.configurationKey = this.ocppConfiguration.configurationKey;
+        }
+        if (params.connectors && this.connectors.size > 0) {
+          configurationData.connectorsStatus = [...this.connectors.values()].map(
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
+            ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest
+          );
+        }
+        if (params.evses && this.evses.size > 0) {
+          configurationData.evsesStatus = [...this.evses.values()].map((evseStatus) => {
+            const status = {
+              ...evseStatus,
+              connectorsStatus: [...evseStatus.connectors.values()].map(
+                // eslint-disable-next-line @typescript-eslint/no-unused-vars
+                ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest
+              ),
+            };
+            delete status.connectors;
+            return status as EvseStatusConfiguration;
+          });
+        }
         delete configurationData.configurationHash;
         const configurationHash = crypto
           .createHash(Constants.DEFAULT_HASH_ALGORITHM)
@@ -1758,18 +1862,38 @@ export class ChargingStation {
 
   private getNumberOfRunningTransactions(): number {
     let trxCount = 0;
-    for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-        trxCount++;
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.transactionStarted === true) {
+            trxCount++;
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+          trxCount++;
+        }
       }
     }
     return trxCount;
   }
 
   private async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise<void> {
-    for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-        await this.stopTransactionOnConnector(connectorId, reason);
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (connectorStatus.transactionStarted === true) {
+            await this.stopTransactionOnConnector(connectorId, reason);
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+          await this.stopTransactionOnConnector(connectorId, reason);
+        }
       }
     }
   }
@@ -1868,30 +1992,34 @@ export class ChargingStation {
     // Start heartbeat
     this.startHeartbeat();
     // Initialize connectors status
-    for (const connectorId of this.connectors.keys()) {
-      let connectorStatus: ConnectorStatusEnum | undefined;
-      if (connectorId === 0) {
-        continue;
-      } else if (
-        !this.getConnectorStatus(connectorId)?.status &&
-        (this.isChargingStationAvailable() === false ||
-          this.isConnectorAvailable(connectorId) === false)
-      ) {
-        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;
-      } else if (this.getConnectorStatus(connectorId)?.status) {
-        // Set previous status at startup
-        connectorStatus = this.getConnectorStatus(connectorId)?.status;
-      } else {
-        // Set default status
-        connectorStatus = ConnectorStatusEnum.Available;
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+            const connectorBootStatus = ChargingStationUtils.getBootConnectorStatus(
+              this,
+              connectorId,
+              connectorStatus
+            );
+            await OCPPServiceUtils.sendAndSetConnectorStatus(
+              this,
+              connectorId,
+              connectorBootStatus
+            );
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0) {
+          const connectorBootStatus = ChargingStationUtils.getBootConnectorStatus(
+            this,
+            connectorId,
+            this.getConnectorStatus(connectorId)
+          );
+          await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorBootStatus);
+        }
       }
-      await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorStatus);
     }
     if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
       await this.ocppRequestService.requestHandler<
@@ -1923,21 +2051,43 @@ export class ChargingStation {
     } else {
       await this.stopRunningTransactions(reason);
     }
-    for (const connectorId of this.connectors.keys()) {
-      if (connectorId > 0) {
-        await this.ocppRequestService.requestHandler<
-          StatusNotificationRequest,
-          StatusNotificationResponse
-        >(
-          this,
-          RequestCommand.STATUS_NOTIFICATION,
-          OCPPServiceUtils.buildStatusNotificationRequest(
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+            await this.ocppRequestService.requestHandler<
+              StatusNotificationRequest,
+              StatusNotificationResponse
+            >(
+              this,
+              RequestCommand.STATUS_NOTIFICATION,
+              OCPPServiceUtils.buildStatusNotificationRequest(
+                this,
+                connectorId,
+                ConnectorStatusEnum.Unavailable
+              )
+            );
+            delete connectorStatus?.status;
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0) {
+          await this.ocppRequestService.requestHandler<
+            StatusNotificationRequest,
+            StatusNotificationResponse
+          >(
             this,
-            connectorId,
-            ConnectorStatusEnum.Unavailable
-          )
-        );
-        delete this.getConnectorStatus(connectorId)?.status;
+            RequestCommand.STATUS_NOTIFICATION,
+            OCPPServiceUtils.buildStatusNotificationRequest(
+              this,
+              connectorId,
+              ConnectorStatusEnum.Unavailable
+            )
+          );
+          delete this.getConnectorStatus(connectorId)?.status;
+        }
       }
     }
   }