build(deps): apply updates
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 645c3f736b004be126891afc3f8bb8b2318c85dc..92f15e4c20f7c71fdd5f1fffe55723eec306532e 100644 (file)
@@ -10,11 +10,11 @@ import merge from 'just-merge';
 import WebSocket, { type RawData } from 'ws';
 
 import {
-  AuthorizedTagsCache,
   AutomaticTransactionGenerator,
   ChargingStationConfigurationUtils,
   ChargingStationUtils,
   ChargingStationWorkerBroadcastChannel,
+  IdTagsCache,
   MessageChannelUtils,
   SharedLRUCache,
 } from './internal';
@@ -47,7 +47,7 @@ import {
   type ChargingStationOcppConfiguration,
   type ChargingStationTemplate,
   ConnectorPhaseRotation,
-  ConnectorStatus,
+  type ConnectorStatus,
   ConnectorStatusEnum,
   CurrentType,
   type ErrorCallback,
@@ -74,7 +74,6 @@ import {
   RegistrationStatusEnumType,
   RequestCommand,
   type Response,
-  type ResponseCallback,
   StandardParametersKey,
   type StatusNotificationRequest,
   type StatusNotificationResponse,
@@ -104,7 +103,7 @@ export class ChargingStation {
   public stationInfo!: ChargingStationInfo;
   public started: boolean;
   public starting: boolean;
-  public authorizedTagsCache: AuthorizedTagsCache;
+  public idTagsCache: IdTagsCache;
   public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
   public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
   public wsConnection!: WebSocket | null;
@@ -123,7 +122,6 @@ export class ChargingStation {
   private ocppIncomingRequestService!: OCPPIncomingRequestService;
   private readonly messageBuffer: Set<string>;
   private configuredSupervisionUrl!: URL;
-  private configuredSupervisionUrlIndex!: number;
   private wsConnectionRestarted: boolean;
   private autoReconnectRetryCount: number;
   private templateFileWatcher!: fs.FSWatcher | undefined;
@@ -143,7 +141,7 @@ export class ChargingStation {
     this.requests = new Map<string, CachedRequest>();
     this.messageBuffer = new Set<string>();
     this.sharedLRUCache = SharedLRUCache.getInstance();
-    this.authorizedTagsCache = AuthorizedTagsCache.getInstance();
+    this.idTagsCache = IdTagsCache.getInstance();
     this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
 
     this.initialize();
@@ -152,7 +150,8 @@ export class ChargingStation {
   private get wsConnectionUrl(): URL {
     return new URL(
       `${
-        this.getSupervisionUrlOcppConfiguration()
+        this.getSupervisionUrlOcppConfiguration() &&
+        Utils.isNotEmptyString(this.getSupervisionUrlOcppKey())
           ? ChargingStationConfigurationUtils.getConfigurationKey(
               this,
               this.getSupervisionUrlOcppKey()
@@ -165,20 +164,17 @@ export class ChargingStation {
   public logPrefix = (): string => {
     return Utils.logPrefix(
       ` ${
-        (Utils.isNotEmptyString(this?.stationInfo?.chargingStationId) &&
-          this?.stationInfo?.chargingStationId) ??
-        ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile()) ??
-        ''
+        (Utils.isNotEmptyString(this?.stationInfo?.chargingStationId)
+          ? this?.stationInfo?.chargingStationId
+          : ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile())) ??
+        'Error at building log prefix'
       } |`
     );
   };
 
-  public hasAuthorizedTags(): boolean {
-    return Utils.isNotEmptyArray(
-      this.authorizedTagsCache.getAuthorizedTags(
-        ChargingStationUtils.getAuthorizationFile(this.stationInfo)
-      )
-    );
+  public hasIdTags(): boolean {
+    const idTagsFile = ChargingStationUtils.getIdTagsFile(this.stationInfo);
+    return Utils.isNotEmptyArray(this.idTagsCache.getIdTags(idTagsFile));
   }
 
   public getEnableStatistics(): boolean {
@@ -387,12 +383,49 @@ export class ChargingStation {
     return localAuthListEnabled ? Utils.convertToBoolean(localAuthListEnabled.value) : false;
   }
 
-  public startHeartbeat(): void {
+  public getHeartbeatInterval(): number {
+    const HeartbeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
+      this,
+      StandardParametersKey.HeartbeatInterval
+    );
+    if (HeartbeatInterval) {
+      return Utils.convertToInt(HeartbeatInterval.value) * 1000;
+    }
+    const HeartBeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
+      this,
+      StandardParametersKey.HeartBeatInterval
+    );
+    if (HeartBeatInterval) {
+      return Utils.convertToInt(HeartBeatInterval.value) * 1000;
+    }
+    this.stationInfo?.autoRegister === false &&
+      logger.warn(
+        `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${
+          Constants.DEFAULT_HEARTBEAT_INTERVAL
+        }`
+      );
+    return Constants.DEFAULT_HEARTBEAT_INTERVAL;
+  }
+
+  public setSupervisionUrl(url: string): void {
     if (
-      this.getHeartbeatInterval() &&
-      this.getHeartbeatInterval() > 0 &&
-      !this.heartbeatSetInterval
+      this.getSupervisionUrlOcppConfiguration() &&
+      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey())
     ) {
+      ChargingStationConfigurationUtils.setConfigurationKeyValue(
+        this,
+        this.getSupervisionUrlOcppKey(),
+        url
+      );
+    } else {
+      this.stationInfo.supervisionUrls = url;
+      this.saveStationInfo();
+      this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
+    }
+  }
+
+  public startHeartbeat(): void {
+    if (this.getHeartbeatInterval() > 0 && !this.heartbeatSetInterval) {
       this.heartbeatSetInterval = setInterval(() => {
         this.ocppRequestService
           .requestHandler<HeartbeatRequest, HeartbeatResponse>(this, RequestCommand.HEARTBEAT)
@@ -416,11 +449,7 @@ export class ChargingStation {
       );
     } else {
       logger.error(
-        `${this.logPrefix()} Heartbeat interval set to ${
-          this.getHeartbeatInterval()
-            ? Utils.formatDurationMilliSeconds(this.getHeartbeatInterval())
-            : this.getHeartbeatInterval()
-        }, not starting the heartbeat`
+        `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat`
       );
     }
   }
@@ -496,9 +525,7 @@ export class ChargingStation {
       logger.error(
         `${this.logPrefix()} Charging station ${
           StandardParametersKey.MeterValueSampleInterval
-        } configuration set to ${
-          interval ? Utils.formatDurationMilliSeconds(interval) : interval
-        }, not sending MeterValues`
+        } configuration set to ${interval}, not sending MeterValues`
       );
     }
   }
@@ -625,7 +652,7 @@ export class ChargingStation {
   }
 
   public openWSConnection(
-    options: WsOptions = this.stationInfo?.wsOptions ?? Constants.EMPTY_OBJECT,
+    options: WsOptions = this.stationInfo?.wsOptions ?? {},
     params: { closeOpened?: boolean; terminateOpened?: boolean } = {
       closeOpened: false,
       terminateOpened: false,
@@ -769,7 +796,7 @@ export class ChargingStation {
 
   private flushMessageBuffer(): void {
     if (this.messageBuffer.size > 0) {
-      this.messageBuffer.forEach((message) => {
+      for (const message of this.messageBuffer.values()) {
         let beginId: string;
         let commandName: RequestCommand;
         const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse;
@@ -786,7 +813,7 @@ export class ChargingStation {
           )} payload sent: ${message}`
         );
         this.messageBuffer.delete(message);
-      });
+      }
     }
   }
 
@@ -839,18 +866,10 @@ export class ChargingStation {
       logger.error(`${this.logPrefix()} ${errorMsg}`);
       throw new BaseError(errorMsg);
     }
-    // Deprecation template keys section
-    ChargingStationUtils.warnDeprecatedTemplateKey(
-      stationTemplate,
-      'supervisionUrl',
+    ChargingStationUtils.warnTemplateKeysDeprecation(
       this.templateFile,
-      this.logPrefix(),
-      "Use 'supervisionUrls' instead"
-    );
-    ChargingStationUtils.convertDeprecatedTemplateKey(
       stationTemplate,
-      'supervisionUrl',
-      'supervisionUrls'
+      this.logPrefix()
     );
     const stationInfo: ChargingStationInfo =
       ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
@@ -894,7 +913,7 @@ export class ChargingStation {
         },
         reset: true,
       },
-      stationTemplate?.firmwareUpgrade ?? Constants.EMPTY_OBJECT
+      stationTemplate?.firmwareUpgrade ?? {}
     );
     stationInfo.resetTime = !Utils.isNullOrUndefined(stationTemplate?.resetTime)
       ? stationTemplate.resetTime * 1000
@@ -943,7 +962,10 @@ export class ChargingStation {
   private getStationInfo(): ChargingStationInfo {
     const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate();
     const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile();
-    // Priority: charging station info from template > charging station info from configuration file > charging station info attribute
+    // Priority:
+    // 1. charging station info from template
+    // 2. charging station info from configuration file
+    // 3. charging station info attribute
     if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) {
       if (this.stationInfo?.infoHash === stationInfoFromFile?.infoHash) {
         return this.stationInfo;
@@ -985,6 +1007,24 @@ export class ChargingStation {
       `${ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile())}.json`
     );
     this.stationInfo = this.getStationInfo();
+    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('.');
+    }
     this.saveStationInfo();
     // Avoid duplication of connectors related information in RAM
     delete this.stationInfo?.Connectors;
@@ -1011,24 +1051,6 @@ export class ChargingStation {
         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 {
@@ -1083,6 +1105,7 @@ export class ChargingStation {
     }
     if (
       this.getSupervisionUrlOcppConfiguration() &&
+      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
       !ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
     ) {
       ChargingStationConfigurationUtils.addConfigurationKey(
@@ -1093,6 +1116,7 @@ export class ChargingStation {
       );
     } else if (
       !this.getSupervisionUrlOcppConfiguration() &&
+      Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
       ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
     ) {
       ChargingStationConfigurationUtils.deleteConfigurationKey(
@@ -1299,8 +1323,7 @@ export class ChargingStation {
       }
       if (
         connectorId > 0 &&
-        (this.getConnectorStatus(connectorId)?.transactionStarted === undefined ||
-          this.getConnectorStatus(connectorId)?.transactionStarted === null)
+        Utils.isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionStarted)
       ) {
         this.initializeConnectorStatus(connectorId);
       }
@@ -1358,7 +1381,7 @@ export class ChargingStation {
           fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
         }
         const configurationData: ChargingStationConfiguration =
-          Utils.cloneObject(this.getConfigurationFromFile()) ?? Constants.EMPTY_OBJECT;
+          Utils.cloneObject(this.getConfigurationFromFile()) ?? {};
         this.ocppConfiguration?.configurationKey &&
           (configurationData.configurationKey = this.ocppConfiguration.configurationKey);
         this.stationInfo && (configurationData.stationInfo = this.stationInfo);
@@ -1510,7 +1533,7 @@ export class ChargingStation {
         messageType
       )} is not an array`,
       undefined,
-      cachedRequest as unknown as JsonType
+      cachedRequest as JsonType
     );
   }
 
@@ -1824,15 +1847,7 @@ export class ChargingStation {
         // Set default status
         connectorStatus = ConnectorStatusEnum.Available;
       }
-      await this.ocppRequestService.requestHandler<
-        StatusNotificationRequest,
-        StatusNotificationResponse
-      >(
-        this,
-        RequestCommand.STATUS_NOTIFICATION,
-        OCPPServiceUtils.buildStatusNotificationRequest(this, connectorId, connectorStatus)
-      );
-      this.getConnectorStatus(connectorId).status = connectorStatus;
+      await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorStatus);
     }
     if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
       await this.ocppRequestService.requestHandler<
@@ -1914,11 +1929,7 @@ export class ChargingStation {
       );
     } else {
       logger.error(
-        `${this.logPrefix()} WebSocket ping interval set to ${
-          webSocketPingInterval
-            ? Utils.formatDurationSeconds(webSocketPingInterval)
-            : webSocketPingInterval
-        }, not starting the WebSocket ping`
+        `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval}, not starting the WebSocket ping`
       );
     }
   }
@@ -1926,17 +1937,17 @@ export class ChargingStation {
   private stopWebSocketPing(): void {
     if (this.webSocketPingSetInterval) {
       clearInterval(this.webSocketPingSetInterval);
+      delete this.webSocketPingSetInterval;
     }
   }
 
   private getConfiguredSupervisionUrl(): URL {
     const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls();
     if (Utils.isNotEmptyArray(supervisionUrls)) {
+      let configuredSupervisionUrlIndex: number;
       switch (Configuration.getSupervisionUrlDistribution()) {
         case SupervisionUrlDistribution.RANDOM:
-          this.configuredSupervisionUrlIndex = Math.floor(
-            Utils.secureRandom() * supervisionUrls.length
-          );
+          configuredSupervisionUrlIndex = Math.floor(Utils.secureRandom() * supervisionUrls.length);
           break;
         case SupervisionUrlDistribution.ROUND_ROBIN:
         case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
@@ -1949,41 +1960,18 @@ export class ChargingStation {
                 SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
               }`
             );
-          this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
+          configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
           break;
       }
-      return new URL(supervisionUrls[this.configuredSupervisionUrlIndex]);
+      return new URL(supervisionUrls[configuredSupervisionUrlIndex]);
     }
     return new URL(supervisionUrls as string);
   }
 
-  private getHeartbeatInterval(): number {
-    const HeartbeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
-      this,
-      StandardParametersKey.HeartbeatInterval
-    );
-    if (HeartbeatInterval) {
-      return Utils.convertToInt(HeartbeatInterval.value) * 1000;
-    }
-    const HeartBeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
-      this,
-      StandardParametersKey.HeartBeatInterval
-    );
-    if (HeartBeatInterval) {
-      return Utils.convertToInt(HeartBeatInterval.value) * 1000;
-    }
-    this.stationInfo?.autoRegister === false &&
-      logger.warn(
-        `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${
-          Constants.DEFAULT_HEARTBEAT_INTERVAL
-        }`
-      );
-    return Constants.DEFAULT_HEARTBEAT_INTERVAL;
-  }
-
   private stopHeartbeat(): void {
     if (this.heartbeatSetInterval) {
       clearInterval(this.heartbeatSetInterval);
+      delete this.heartbeatSetInterval;
     }
   }
 
@@ -2038,7 +2026,7 @@ export class ChargingStation {
       );
       this.openWSConnection(
         {
-          ...(this.stationInfo?.wsOptions ?? Constants.EMPTY_OBJECT),
+          ...(this.stationInfo?.wsOptions ?? {}),
           handshakeTimeout: reconnectTimeout,
         },
         { closeOpened: true }