Hook the OCPP 2.0 stack into the main code
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationUtils.ts
index 7208fb80fe383fa916836306d63c33d9405f634a..fd0dd7651f02a1e65fc5cbf00bed5e25634ce0ef 100644 (file)
@@ -1,31 +1,33 @@
-import { ChargingProfile, ChargingSchedulePeriod } from '../types/ocpp/ChargingProfile';
-import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile';
-import ChargingStationTemplate, {
+import crypto from 'crypto';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+import moment from 'moment';
+
+import BaseError from '../exception/BaseError';
+import type { ChargingStationInfo } from '../types/ChargingStationInfo';
+import {
   AmpereUnits,
+  type ChargingStationTemplate,
   CurrentType,
   Voltage,
 } from '../types/ChargingStationTemplate';
-import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues';
-
-import BaseError from '../exception/BaseError';
-import { BootNotificationRequest } from '../types/ocpp/Requests';
-import ChargingStation from './ChargingStation';
-import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
-import ChargingStationInfo from '../types/ChargingStationInfo';
+import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile';
+import type { ChargingProfile, ChargingSchedulePeriod } from '../types/ocpp/ChargingProfile';
+import type { BootNotificationRequest } from '../types/ocpp/Requests';
+import { WorkerProcessType } from '../types/Worker';
 import Configuration from '../utils/Configuration';
 import Constants from '../utils/Constants';
-import { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates';
-import { StandardParametersKey } from '../types/ocpp/Configuration';
-import Utils from '../utils/Utils';
-import { WebSocketCloseEventStatusString } from '../types/WebSocket';
-import { WorkerProcessType } from '../types/Worker';
-import crypto from 'crypto';
-import { fileURLToPath } from 'url';
 import logger from '../utils/Logger';
-import moment from 'moment';
-import path from 'path';
+import Utils from '../utils/Utils';
+
+const moduleName = 'ChargingStationUtils';
 
 export class ChargingStationUtils {
+  private constructor() {
+    // This is intentional
+  }
+
   public static getChargingStationId(
     index: number,
     stationTemplate: ChargingStationTemplate
@@ -53,6 +55,7 @@ export class ChargingStationUtils {
       ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
         chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
       }),
+      // FIXME?: Should a firmware version change always reference a new configuration file?
       ...(!Utils.isUndefined(stationTemplate.firmwareVersion) && {
         firmwareVersion: stationTemplate.firmwareVersion,
       }),
@@ -98,16 +101,13 @@ export class ChargingStationUtils {
     }
   }
 
-  public static getConfiguredNumberOfConnectors(
-    index: number,
-    stationTemplate: ChargingStationTemplate
-  ): number {
+  public static getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number {
     let configuredMaxConnectors: number;
-    if (!Utils.isEmptyArray(stationTemplate.numberOfConnectors)) {
+    if (Utils.isEmptyArray(stationTemplate.numberOfConnectors) === false) {
       const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
-      // Distribute evenly the number of connectors
-      configuredMaxConnectors = numberOfConnectors[(index - 1) % numberOfConnectors.length];
-    } else if (!Utils.isUndefined(stationTemplate.numberOfConnectors)) {
+      configuredMaxConnectors =
+        numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)];
+    } else if (Utils.isUndefined(stationTemplate.numberOfConnectors) === false) {
       configuredMaxConnectors = stationTemplate.numberOfConnectors as number;
     } else {
       configuredMaxConnectors = stationTemplate?.Connectors[0]
@@ -165,32 +165,6 @@ export class ChargingStationUtils {
     return Configuration.getWorker().processType === WorkerProcessType.DYNAMIC_POOL;
   }
 
-  /**
-   * Convert websocket error code to human readable string message
-   *
-   * @param code websocket error code
-   * @returns human readable string message
-   */
-  public static getWebSocketCloseEventStatusString(code: number): string {
-    if (code >= 0 && code <= 999) {
-      return '(Unused)';
-    } else if (code >= 1016) {
-      if (code <= 1999) {
-        return '(For WebSocket standard)';
-      } else if (code <= 2999) {
-        return '(For WebSocket extensions)';
-      } else if (code <= 3999) {
-        return '(For libraries and frameworks)';
-      } else if (code <= 4999) {
-        return '(For applications)';
-      }
-    }
-    if (!Utils.isUndefined(WebSocketCloseEventStatusString[code])) {
-      return WebSocketCloseEventStatusString[code] as string;
-    }
-    return '(Unknown)';
-  }
-
   public static warnDeprecatedTemplateKey(
     template: ChargingStationTemplate,
     key: string,
@@ -229,7 +203,7 @@ export class ChargingStationUtils {
     delete stationTemplate.chargeBoxSerialNumberPrefix;
     delete stationTemplate.chargePointSerialNumberPrefix;
     delete stationTemplate.meterSerialNumberPrefix;
-    return stationTemplate;
+    return stationTemplate as unknown as ChargingStationInfo;
   }
 
   public static createStationInfoHash(stationInfo: ChargingStationInfo): void {
@@ -310,9 +284,9 @@ export class ChargingStationUtils {
   /**
    * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
    *
-   * @param {ChargingProfile[]} chargingProfiles
-   * @param {string} logPrefix
-   * @returns {{ limit, matchingChargingProfile }}
+   * @param chargingProfiles -
+   * @param logPrefix -
+   * @returns
    */
   public static getLimitFromChargingProfiles(
     chargingProfiles: ChargingProfile[],
@@ -321,6 +295,7 @@ export class ChargingStationUtils {
     limit: number;
     matchingChargingProfile: ChargingProfile;
   } | null {
+    const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
     for (const chargingProfile of chargingProfiles) {
       // Set helpers
       const currentMoment = moment();
@@ -364,10 +339,7 @@ export class ChargingStationUtils {
               limit: schedulePeriod.limit,
               matchingChargingProfile: chargingProfile,
             };
-            logger.debug(
-              `${logPrefix} Matching charging profile found for power limitation: %j`,
-              result
-            );
+            logger.debug(debugLogMsg, result);
             return result;
           }
           // Find the right schedule period
@@ -381,10 +353,7 @@ export class ChargingStationUtils {
               limit: lastButOneSchedule.limit,
               matchingChargingProfile: chargingProfile,
             };
-            logger.debug(
-              `${logPrefix} Matching charging profile found for power limitation: %j`,
-              result
-            );
+            logger.debug(debugLogMsg, result);
             return result;
           }
           // Keep it
@@ -400,10 +369,7 @@ export class ChargingStationUtils {
               limit: lastButOneSchedule.limit,
               matchingChargingProfile: chargingProfile,
             };
-            logger.debug(
-              `${logPrefix} Matching charging profile found for power limitation: %j`,
-              result
-            );
+            logger.debug(debugLogMsg, result);
             return result;
           }
         }
@@ -417,7 +383,7 @@ export class ChargingStationUtils {
     templateFile: string,
     logPrefix: string
   ): Voltage {
-    const errMsg = `${logPrefix} Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
+    const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
     let defaultVoltageOut: number;
     switch (currentType) {
       case CurrentType.AC:
@@ -427,93 +393,12 @@ export class ChargingStationUtils {
         defaultVoltageOut = Voltage.VOLTAGE_400;
         break;
       default:
-        logger.error(errMsg);
-        throw new Error(errMsg);
+        logger.error(`${logPrefix} ${errMsg}`);
+        throw new BaseError(errMsg);
     }
     return defaultVoltageOut;
   }
 
-  public static getSampledValueTemplate(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
-    phase?: MeterValuePhase
-  ): SampledValueTemplate | undefined {
-    const onPhaseStr = phase ? `on phase ${phase} ` : '';
-    if (!Constants.SUPPORTED_MEASURANDS.includes(measurand)) {
-      logger.warn(
-        `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
-      );
-      return;
-    }
-    if (
-      measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        chargingStation,
-        StandardParametersKey.MeterValuesSampledData
-      )?.value.includes(measurand)
-    ) {
-      logger.debug(
-        `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
-          StandardParametersKey.MeterValuesSampledData
-        }' OCPP parameter`
-      );
-      return;
-    }
-    const sampledValueTemplates: SampledValueTemplate[] =
-      chargingStation.getConnectorStatus(connectorId).MeterValues;
-    for (
-      let index = 0;
-      !Utils.isEmptyArray(sampledValueTemplates) && index < sampledValueTemplates.length;
-      index++
-    ) {
-      if (
-        !Constants.SUPPORTED_MEASURANDS.includes(
-          sampledValueTemplates[index]?.measurand ??
-            MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-        )
-      ) {
-        logger.warn(
-          `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
-        );
-      } else if (
-        phase &&
-        sampledValueTemplates[index]?.phase === phase &&
-        sampledValueTemplates[index]?.measurand === measurand &&
-        ChargingStationConfigurationUtils.getConfigurationKey(
-          chargingStation,
-          StandardParametersKey.MeterValuesSampledData
-        )?.value.includes(measurand)
-      ) {
-        return sampledValueTemplates[index];
-      } else if (
-        !phase &&
-        !sampledValueTemplates[index].phase &&
-        sampledValueTemplates[index]?.measurand === measurand &&
-        ChargingStationConfigurationUtils.getConfigurationKey(
-          chargingStation,
-          StandardParametersKey.MeterValuesSampledData
-        )?.value.includes(measurand)
-      ) {
-        return sampledValueTemplates[index];
-      } else if (
-        measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
-        (!sampledValueTemplates[index].measurand ||
-          sampledValueTemplates[index].measurand === measurand)
-      ) {
-        return sampledValueTemplates[index];
-      }
-    }
-    if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
-      const errorMsg = `${chargingStation.logPrefix()} Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
-      logger.error(errorMsg);
-      throw new Error(errorMsg);
-    }
-    logger.debug(
-      `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
-    );
-  }
-
   public static getAuthorizationFile(stationInfo: ChargingStationInfo): string | undefined {
     return (
       stationInfo.authorizationFile &&