refactor: stricter type checking in conditions
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 4525b8b7a45736e0e791ca43fcff613eb6051a8a..6d4b45f3459b426bdc27c62b7602acf059484f1e 100644 (file)
@@ -114,6 +114,7 @@ import {
   SupervisionUrlDistribution,
   SupportedFeatureProfiles,
   VendorParametersKey,
+  Voltage,
   type WSError,
   WebSocketCloseEventStatusCode,
   type WsOptions,
@@ -232,14 +233,14 @@ export class ChargingStation extends EventEmitter {
         enableStatistics: false,
         remoteAuthorization: true,
         currentOutType: CurrentType.AC,
+        mainVoltageMeterValues: true,
+        phaseLineToLineVoltageMeterValues: false,
+        customValueLimitationMeterValues: true,
         ocppStrictCompliance: true,
         outOfOrderEndMeterValues: false,
         beginEndMeterValues: false,
         meteringPerTransaction: true,
         transactionDataMeterValues: false,
-        mainVoltageMeterValues: true,
-        phaseLineToLineVoltageMeterValues: false,
-        customValueLimitationMeterValues: true,
         supervisionUrlOcppConfiguration: false,
         supervisionUrlOcppKey: VendorParametersKey.ConnectionUrl,
         ocppVersion: OCPPVersion.VERSION_16,
@@ -258,7 +259,7 @@ export class ChargingStation extends EventEmitter {
   private get wsConnectionUrl(): URL {
     return new URL(
       `${
-        this.stationInfo?.supervisionUrlOcppConfiguration &&
+        this.stationInfo?.supervisionUrlOcppConfiguration === true &&
         isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
         isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)?.value)
           ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)!.value
@@ -286,7 +287,7 @@ export class ChargingStation extends EventEmitter {
     const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
     switch (this.getCurrentOutType(stationInfo)) {
       case CurrentType.AC:
-        return !isUndefined(localStationInfo.numberOfPhases) ? localStationInfo.numberOfPhases! : 3;
+        return localStationInfo.numberOfPhases ?? 3;
       case CurrentType.DC:
         return 0;
     }
@@ -482,7 +483,9 @@ export class ChargingStation extends EventEmitter {
       this,
       StandardParametersKey.AuthorizeRemoteTxRequests,
     );
-    return authorizeRemoteTxRequests ? convertToBoolean(authorizeRemoteTxRequests.value) : false;
+    return authorizeRemoteTxRequests !== undefined
+      ? convertToBoolean(authorizeRemoteTxRequests.value)
+      : false;
   }
 
   public getLocalAuthListEnabled(): boolean {
@@ -490,16 +493,18 @@ export class ChargingStation extends EventEmitter {
       this,
       StandardParametersKey.LocalAuthListEnabled,
     );
-    return localAuthListEnabled ? convertToBoolean(localAuthListEnabled.value) : false;
+    return localAuthListEnabled !== undefined
+      ? convertToBoolean(localAuthListEnabled.value)
+      : false;
   }
 
   public getHeartbeatInterval(): number {
     const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval);
-    if (HeartbeatInterval) {
+    if (HeartbeatInterval !== undefined) {
       return secondsToMilliseconds(convertToInt(HeartbeatInterval.value));
     }
     const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval);
-    if (HeartBeatInterval) {
+    if (HeartBeatInterval !== undefined) {
       return secondsToMilliseconds(convertToInt(HeartBeatInterval.value));
     }
     this.stationInfo?.autoRegister === false &&
@@ -513,7 +518,7 @@ export class ChargingStation extends EventEmitter {
 
   public setSupervisionUrl(url: string): void {
     if (
-      this.stationInfo?.supervisionUrlOcppConfiguration &&
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
       isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey)
     ) {
       setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey!, url);
@@ -915,11 +920,8 @@ export class ChargingStation extends EventEmitter {
 
   public async addReservation(reservation: Reservation): Promise<void> {
     const reservationFound = this.getReservationBy('reservationId', reservation.reservationId);
-    if (!isUndefined(reservationFound)) {
-      await this.removeReservation(
-        reservationFound!,
-        ReservationTerminationReason.REPLACE_EXISTING,
-      );
+    if (reservationFound !== undefined) {
+      await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING);
     }
     this.getConnectorStatus(reservation.connectorId)!.reservation = reservation;
     await OCPPServiceUtils.sendAndSetConnectorStatus(
@@ -1063,14 +1065,17 @@ export class ChargingStation extends EventEmitter {
           [, , commandName] = JSON.parse(message) as OutgoingRequest;
           beginId = PerformanceStatistics.beginMeasure(commandName);
         }
-        this.wsConnection?.send(message);
-        isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!);
-        logger.debug(
-          `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString(
-            messageType,
-          )} payload sent: ${message}`,
-        );
-        this.messageBuffer.delete(message);
+        this.wsConnection?.send(message, (error?: Error) => {
+          isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!);
+          if (isNullOrUndefined(error)) {
+            logger.debug(
+              `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString(
+                messageType,
+              )} payload sent: ${message}`,
+            );
+            this.messageBuffer.delete(message);
+          }
+        });
       }
     }
   }
@@ -1115,6 +1120,7 @@ export class ChargingStation extends EventEmitter {
     stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate);
     stationInfo.ocppVersion = stationTemplate?.ocppVersion ?? OCPPVersion.VERSION_16;
     createSerialNumber(stationTemplate, stationInfo);
+    stationInfo.voltageOut = this.getVoltageOut(stationInfo);
     if (isNotEmptyArray(stationTemplate?.power)) {
       stationTemplate.power = stationTemplate.power as number[];
       const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length);
@@ -1192,7 +1198,7 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  private handleUnsupportedVersion(version: OCPPVersion) {
+  private handleUnsupportedVersion(version: OCPPVersion | undefined) {
     const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
     logger.error(`${this.logPrefix()} ${errorMsg}`);
     throw new BaseError(errorMsg);
@@ -1249,6 +1255,11 @@ export class ChargingStation extends EventEmitter {
     this.ocppConfiguration = this.getOcppConfiguration();
     this.initializeOcppConfiguration();
     this.initializeOcppServices();
+    this.once(ChargingStationEvents.accepted, () => {
+      this.startMessageSequence().catch((error) => {
+        logger.error(`${this.logPrefix()} Error while starting the message sequence:`, error);
+      });
+    });
     if (this.stationInfo?.autoRegister === true) {
       this.bootNotificationResponse = {
         currentTime: new Date(),
@@ -1283,16 +1294,16 @@ export class ChargingStation extends EventEmitter {
   }
 
   private initializeOcppConfiguration(): void {
-    if (!getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)) {
+    if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.HeartbeatInterval))) {
       addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0');
     }
-    if (!getConfigurationKey(this, StandardParametersKey.HeartBeatInterval)) {
+    if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.HeartBeatInterval))) {
       addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', { visible: false });
     }
     if (
-      this.stationInfo?.supervisionUrlOcppConfiguration &&
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
       isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
-      !getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)
+      isNullOrUndefined(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!))
     ) {
       addConfigurationKey(
         this,
@@ -1301,7 +1312,7 @@ export class ChargingStation extends EventEmitter {
         { reboot: true },
       );
     } else if (
-      !this.stationInfo?.supervisionUrlOcppConfiguration &&
+      this.stationInfo?.supervisionUrlOcppConfiguration === false &&
       isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
       getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)
     ) {
@@ -1309,7 +1320,7 @@ export class ChargingStation extends EventEmitter {
     }
     if (
       isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      !getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)
+      isNullOrUndefined(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!))
     ) {
       addConfigurationKey(
         this,
@@ -1319,7 +1330,9 @@ export class ChargingStation extends EventEmitter {
         ).toString(),
       );
     }
-    if (!getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)) {
+    if (
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles))
+    ) {
       addConfigurationKey(
         this,
         StandardParametersKey.SupportedFeatureProfiles,
@@ -1333,14 +1346,18 @@ export class ChargingStation extends EventEmitter {
       { readonly: true },
       { overwrite: true },
     );
-    if (!getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData)) {
+    if (
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData))
+    ) {
       addConfigurationKey(
         this,
         StandardParametersKey.MeterValuesSampledData,
         MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
       );
     }
-    if (!getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation)) {
+    if (
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation))
+    ) {
       const connectorsPhaseRotation: string[] = [];
       if (this.hasEvses) {
         for (const evseStatus of this.evses.values()) {
@@ -1363,18 +1380,20 @@ export class ChargingStation extends EventEmitter {
         connectorsPhaseRotation.toString(),
       );
     }
-    if (!getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests)) {
+    if (
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests))
+    ) {
       addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true');
     }
     if (
-      !getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) &&
+      isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled)) &&
       getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)?.value?.includes(
         SupportedFeatureProfiles.LocalAuthListManagement,
       )
     ) {
       addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false');
     }
-    if (!getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)) {
+    if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut))) {
       addConfigurationKey(
         this,
         StandardParametersKey.ConnectionTimeOut,
@@ -1763,7 +1782,6 @@ export class ChargingStation extends EventEmitter {
         this.emit(ChargingStationEvents.registered);
         if (this.inAcceptedState() === true) {
           this.emit(ChargingStationEvents.accepted);
-          await this.startMessageSequence();
         }
       } else {
         logger.error(
@@ -1781,7 +1799,7 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  private async onClose(code: number, reason: Buffer): Promise<void> {
+  private async onClose(code: WebSocketCloseEventStatusCode, reason: Buffer): Promise<void> {
     switch (code) {
       // Normal close
       case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
@@ -1886,7 +1904,7 @@ export class ChargingStation extends EventEmitter {
 
   private async onMessage(data: RawData): Promise<void> {
     let request: IncomingRequest | Response | ErrorResponse | undefined;
-    let messageType: number | undefined;
+    let messageType: MessageType | undefined;
     let errorMsg: string;
     try {
       // eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -2026,10 +2044,10 @@ export class ChargingStation extends EventEmitter {
 
   // 0 for disabling
   private getConnectionTimeout(): number {
-    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)) {
-      return (
-        parseInt(getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)!.value!) ??
-        Constants.DEFAULT_CONNECTION_TIMEOUT
+    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) !== undefined) {
+      return convertToInt(
+        getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)!.value! ??
+          Constants.DEFAULT_CONNECTION_TIMEOUT,
       );
     }
     return Constants.DEFAULT_CONNECTION_TIMEOUT;
@@ -2037,7 +2055,7 @@ export class ChargingStation extends EventEmitter {
 
   private getPowerDivider(): number {
     let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors();
-    if (this.stationInfo?.powerSharedByConnectors) {
+    if (this.stationInfo?.powerSharedByConnectors === true) {
       powerDivider = this.getNumberOfRunningTransactions();
     }
     return powerDivider;
@@ -2065,19 +2083,17 @@ export class ChargingStation extends EventEmitter {
     return (stationInfo ?? this.stationInfo).currentOutType ?? CurrentType.AC;
   }
 
-  private getVoltageOut(stationInfo?: ChargingStationInfo): number {
-    const defaultVoltageOut = getDefaultVoltageOut(
-      this.getCurrentOutType(stationInfo),
-      this.logPrefix(),
-      this.templateFile,
+  private getVoltageOut(stationInfo?: ChargingStationInfo): Voltage {
+    return (
+      (stationInfo ?? this.stationInfo).voltageOut ??
+      getDefaultVoltageOut(this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile)
     );
-    return (stationInfo ?? this.stationInfo).voltageOut ?? defaultVoltageOut;
   }
 
   private getAmperageLimitation(): number | undefined {
     if (
       isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)
+      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!) !== undefined
     ) {
       return (
         convertToInt(
@@ -2152,12 +2168,12 @@ export class ChargingStation extends EventEmitter {
     this.stopWebSocketPing();
     // Stop heartbeat
     this.stopHeartbeat();
-    // Stop ongoing transactions
-    stopTransactions && (await this.stopRunningTransactions(reason));
     // Stop the ATG
     if (this.automaticTransactionGenerator?.started === true) {
       this.stopAutomaticTransactionGenerator();
     }
+    // Stop ongoing transactions
+    stopTransactions && (await this.stopRunningTransactions(reason));
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
         if (evseId > 0) {
@@ -2201,12 +2217,12 @@ export class ChargingStation extends EventEmitter {
   }
 
   private startWebSocketPing(): void {
-    const webSocketPingInterval: number = getConfigurationKey(
-      this,
-      StandardParametersKey.WebSocketPingInterval,
-    )
-      ? convertToInt(getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value)
-      : 0;
+    const webSocketPingInterval: number =
+      getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) !== undefined
+        ? convertToInt(
+            getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value,
+          )
+        : 0;
     if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
       this.webSocketPingSetInterval = setInterval(() => {
         if (this.isWebSocketConnectionOpened() === true) {