refactor: factor out feature profile test
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
index c85ecdd3ee3dba2cac60ce5ae38e79b4fea2d2cd..7f065d30bed7dd29c684edaa358c82c0866cbe9e 100644 (file)
@@ -2,9 +2,11 @@
 
 import type { JSONSchemaType } from 'ajv';
 
-import { type ChargingStation, getIdTagsFile } from '../../../charging-station';
+import { type ChargingStation, getIdTagsFile, hasFeatureProfile } from '../../../charging-station';
 import { OCPPError } from '../../../exception';
 import {
+  type ClearChargingProfileRequest,
+  type ConnectorStatus,
   CurrentType,
   ErrorType,
   type JsonType,
@@ -53,7 +55,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     featureProfile: OCPP16SupportedFeatureProfiles,
     command: OCPP16RequestCommand | OCPP16IncomingRequestCommand,
   ): boolean {
-    if (!chargingStation.hasFeatureProfile(featureProfile)) {
+    if (!hasFeatureProfile(chargingStation, featureProfile)) {
       logger.warn(
         `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
           OCPP16StandardParametersKey.SupportedFeatureProfiles
@@ -147,7 +149,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             OCPP16MeterValueMeasurand.VOLTAGE,
             phaseLineToNeutralValue as OCPP16MeterValuePhase,
           );
-        let voltagePhaseLineToNeutralMeasurandValue: number;
+        let voltagePhaseLineToNeutralMeasurandValue: number | undefined;
         if (voltagePhaseLineToNeutralSampledValueTemplate) {
           const voltagePhaseLineToNeutralSampledValueTemplateValue =
             voltagePhaseLineToNeutralSampledValueTemplate.value
@@ -182,7 +184,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               OCPP16MeterValueMeasurand.VOLTAGE,
               phaseLineToLineValue as OCPP16MeterValuePhase,
             );
-          let voltagePhaseLineToLineMeasurandValue: number;
+          let voltagePhaseLineToLineMeasurandValue: number | undefined;
           if (voltagePhaseLineToLineSampledValueTemplate) {
             const voltagePhaseLineToLineSampledValueTemplateValue =
               voltagePhaseLineToLineSampledValueTemplate.value
@@ -243,7 +245,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     if (powerSampledValueTemplate) {
       OCPP16ServiceUtils.checkMeasurandPowerDivider(
         chargingStation,
-        powerSampledValueTemplate.measurand,
+        powerSampledValueTemplate.measurand!,
       );
       const errMsg = `MeterValues measurand ${
         powerSampledValueTemplate.measurand ??
@@ -254,7 +256,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         powerSampledValueTemplate.measurand ??
         OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       } measurand value`;
-      const powerMeasurandValues = {} as MeasurandValues;
+      const powerMeasurandValues: MeasurandValues = {} as MeasurandValues;
       const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
       const connectorMaximumAvailablePower =
         chargingStation.getConnectorMaximumAvailablePower(connectorId);
@@ -262,7 +264,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       const connectorMaximumPowerPerPhase = Math.round(
         connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(),
       );
-      const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue) ?? 0;
+      const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue!) ?? 0;
       const connectorMinimumPowerPerPhase = Math.round(
         connectorMinimumPower / chargingStation.getNumberOfPhases(),
       );
@@ -281,7 +283,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase1FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L1?.value &&
+              powerPerPhaseSampledValueTemplates.L1?.value &&
               getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L1.value,
@@ -292,7 +294,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase2FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L2?.value &&
+              powerPerPhaseSampledValueTemplates.L2?.value &&
               getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L2.value,
@@ -303,7 +305,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase3FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L3?.value &&
+              powerPerPhaseSampledValueTemplates.L3?.value &&
               getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L3.value,
@@ -314,22 +316,22 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             powerMeasurandValues.L1 =
-              phase1FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
+              (phase1FluctuatedValue as number) ??
+              (defaultFluctuatedPowerPerPhase as number) ??
               getRandomFloatRounded(
                 connectorMaximumPowerPerPhase / unitDivider,
                 connectorMinimumPowerPerPhase / unitDivider,
               );
             powerMeasurandValues.L2 =
-              phase2FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
+              (phase2FluctuatedValue as number) ??
+              (defaultFluctuatedPowerPerPhase as number) ??
               getRandomFloatRounded(
                 connectorMaximumPowerPerPhase / unitDivider,
                 connectorMinimumPowerPerPhase / unitDivider,
               );
             powerMeasurandValues.L3 =
-              phase3FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
+              (phase3FluctuatedValue as number) ??
+              (defaultFluctuatedPowerPerPhase as number) ??
               getRandomFloatRounded(
                 connectorMaximumPowerPerPhase / unitDivider,
                 connectorMinimumPowerPerPhase / unitDivider,
@@ -410,9 +412,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         const phaseValue = `L${phase}-N`;
         meterValue.sampledValue.push(
           OCPP16ServiceUtils.buildSampledValue(
-            (powerPerPhaseSampledValueTemplates[`L${phase}`] as SampledValueTemplate) ??
-              powerSampledValueTemplate,
-            powerMeasurandValues[`L${phase}`] as number,
+            powerPerPhaseSampledValueTemplates[
+              `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
+            ]! ?? powerSampledValueTemplate,
+            powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
             undefined,
             phaseValue as OCPP16MeterValuePhase,
           ),
@@ -478,7 +481,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     if (currentSampledValueTemplate) {
       OCPP16ServiceUtils.checkMeasurandPowerDivider(
         chargingStation,
-        currentSampledValueTemplate.measurand,
+        currentSampledValueTemplate.measurand!,
       );
       const errMsg = `MeterValues measurand ${
         currentSampledValueTemplate.measurand ??
@@ -514,7 +517,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase1FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L1?.value &&
+              currentPerPhaseSampledValueTemplates.L1?.value &&
               getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L1.value,
@@ -525,7 +528,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase2FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L2?.value &&
+              currentPerPhaseSampledValueTemplates.L2?.value &&
               getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L2.value,
@@ -536,7 +539,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase3FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L3?.value &&
+              currentPerPhaseSampledValueTemplates.L3?.value &&
               getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L3.value,
@@ -547,16 +550,16 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             currentMeasurandValues.L1 =
-              phase1FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
+              (phase1FluctuatedValue as number) ??
+              (defaultFluctuatedAmperagePerPhase as number) ??
               getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
             currentMeasurandValues.L2 =
-              phase2FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
+              (phase2FluctuatedValue as number) ??
+              (defaultFluctuatedAmperagePerPhase as number) ??
               getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
             currentMeasurandValues.L3 =
-              phase3FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
+              (phase3FluctuatedValue as number) ??
+              (defaultFluctuatedAmperagePerPhase as number) ??
               getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
           } else {
             currentMeasurandValues.L1 = currentSampledValueTemplate.value
@@ -631,9 +634,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         const phaseValue = `L${phase}`;
         meterValue.sampledValue.push(
           OCPP16ServiceUtils.buildSampledValue(
-            (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
-              currentSampledValueTemplate,
-            currentMeasurandValues[phaseValue] as number,
+            currentPerPhaseSampledValueTemplates[
+              phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
+            ]! ?? currentSampledValueTemplate,
+            currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
             undefined,
             phaseValue as OCPP16MeterValuePhase,
           ),
@@ -667,7 +671,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     if (energySampledValueTemplate) {
       OCPP16ServiceUtils.checkMeasurandPowerDivider(
         chargingStation,
-        energySampledValueTemplate.measurand,
+        energySampledValueTemplate.measurand!,
       );
       const unitDivider =
         energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
@@ -692,18 +696,19 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           )
         : getRandomFloatRounded(connectorMaximumEnergyRounded);
       // Persist previous value on connector
-      if (
-        connector &&
-        isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
-        connector.energyActiveImportRegisterValue >= 0 &&
-        isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
-        connector.transactionEnergyActiveImportRegisterValue >= 0
-      ) {
-        connector.energyActiveImportRegisterValue += energyValueRounded;
-        connector.transactionEnergyActiveImportRegisterValue += energyValueRounded;
-      } else {
-        connector.energyActiveImportRegisterValue = 0;
-        connector.transactionEnergyActiveImportRegisterValue = 0;
+      if (connector) {
+        if (
+          isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
+          connector.energyActiveImportRegisterValue! >= 0 &&
+          isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
+          connector.transactionEnergyActiveImportRegisterValue! >= 0
+        ) {
+          connector.energyActiveImportRegisterValue! += energyValueRounded;
+          connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded;
+        } else {
+          connector.energyActiveImportRegisterValue = 0;
+          connector.transactionEnergyActiveImportRegisterValue = 0;
+        }
       }
       meterValue.sampledValue.push(
         OCPP16ServiceUtils.buildSampledValue(
@@ -721,10 +726,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           `${chargingStation.logPrefix()} MeterValues measurand ${
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${roundTo(
-            interval / (3600 * 1000),
-            4,
-          )}h`,
+          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
         );
       }
     }
@@ -748,7 +750,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
-        sampledValueTemplate,
+        sampledValueTemplate!,
         roundTo((meterStart ?? 0) / unitDivider, 4),
         MeterValueContext.TRANSACTION_BEGIN,
       ),
@@ -773,7 +775,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
-        sampledValueTemplate,
+        sampledValueTemplate!,
         roundTo((meterStop ?? 0) / unitDivider, 4),
         MeterValueContext.TRANSACTION_END,
       ),
@@ -800,7 +802,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       logger.error(
         `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
       );
-      chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
+      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
     }
     if (
       Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
@@ -808,7 +810,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       logger.error(
         `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`,
       );
-      chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
+      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
     }
     let cpReplaced = false;
     if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
@@ -820,7 +822,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             (chargingProfile.stackLevel === cp.stackLevel &&
               chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
           ) {
-            chargingStation.getConnectorStatus(connectorId).chargingProfiles[index] = cp;
+            chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp;
             cpReplaced = true;
           }
         });
@@ -828,6 +830,49 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
   }
 
+  public static clearChargingProfiles = (
+    chargingStation: ChargingStation,
+    commandPayload: ClearChargingProfileRequest,
+    chargingProfiles: OCPP16ChargingProfile[] | undefined,
+  ): boolean => {
+    let clearedCP = false;
+    if (isNotEmptyArray(chargingProfiles)) {
+      chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
+        let clearCurrentCP = false;
+        if (chargingProfile.chargingProfileId === commandPayload.id) {
+          clearCurrentCP = true;
+        }
+        if (
+          !commandPayload.chargingProfilePurpose &&
+          chargingProfile.stackLevel === commandPayload.stackLevel
+        ) {
+          clearCurrentCP = true;
+        }
+        if (
+          !chargingProfile.stackLevel &&
+          chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
+        ) {
+          clearCurrentCP = true;
+        }
+        if (
+          chargingProfile.stackLevel === commandPayload.stackLevel &&
+          chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
+        ) {
+          clearCurrentCP = true;
+        }
+        if (clearCurrentCP) {
+          chargingProfiles.splice(index, 1);
+          logger.debug(
+            `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
+            chargingProfile,
+          );
+          clearedCP = true;
+        }
+      });
+    }
+    return clearedCP;
+  };
+
   public static parseJsonSchemaFile<T extends JsonType>(
     relativePath: string,
     moduleName?: string,
@@ -847,7 +892,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     idTag: string,
   ): Promise<boolean> {
     let authorized = false;
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId);
+    const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!;
     if (OCPP16ServiceUtils.isIdTagLocalAuthorized(chargingStation, idTag)) {
       connectorStatus.localAuthorizeIdTag = idTag;
       connectorStatus.idTagLocalAuthorized = true;
@@ -874,7 +919,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
     const sampledValueLocation =
       sampledValueTemplate?.location ??
-      OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null);
+      OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
     const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
     return {
       ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
@@ -887,7 +932,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
       ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
       ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
-    };
+    } as OCPP16SampledValue;
   }
 
   private static checkMeasurandPowerDivider(
@@ -946,7 +991,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       chargingStation.hasIdTags() === true &&
       isNotEmptyString(
         chargingStation.idTagsCache
-          .getIdTags(getIdTagsFile(chargingStation.stationInfo))
+          .getIdTags(getIdTagsFile(chargingStation.stationInfo)!)
           ?.find((tag) => tag === idTag),
       )
     );