refactor(simulator): make OCPPIncomingRequestService class inherit from
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
index 7592713b3007b85994c458cf2602f0b349e885eb..d0d46c25d50df40ae9fb3f1f4bed5d9fbe196d5d 100644 (file)
@@ -1,36 +1,44 @@
-// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
-import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import type { JSONSchemaType } from 'ajv';
+
+import OCPPError from '../../../exception/OCPPError';
 import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
-import MeasurandPerPhaseSampledValueTemplates, {
+import type { JsonType } from '../../../types/JsonType';
+import type {
+  MeasurandPerPhaseSampledValueTemplates,
   SampledValueTemplate,
 } from '../../../types/MeasurandPerPhaseSampledValueTemplates';
+import type { MeasurandValues } from '../../../types/MeasurandValues';
+import type { OCPP16ChargingProfile } from '../../../types/ocpp/1.6/ChargingProfile';
+import {
+  OCPP16StandardParametersKey,
+  OCPP16SupportedFeatureProfiles,
+} from '../../../types/ocpp/1.6/Configuration';
 import {
   MeterValueContext,
   MeterValueLocation,
   MeterValueUnit,
-  OCPP16MeterValue,
+  type OCPP16MeterValue,
   OCPP16MeterValueMeasurand,
   OCPP16MeterValuePhase,
-  OCPP16SampledValue,
+  type OCPP16SampledValue,
 } from '../../../types/ocpp/1.6/MeterValues';
 import {
-  OCPP16IncomingRequestCommand,
+  type OCPP16IncomingRequestCommand,
   OCPP16RequestCommand,
 } from '../../../types/ocpp/1.6/Requests';
-import {
-  OCPP16StandardParametersKey,
-  OCPP16SupportedFeatureProfiles,
-} from '../../../types/ocpp/1.6/Configuration';
-
-import type ChargingStation from '../../ChargingStation';
-import Constants from '../../../utils/Constants';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
-import MeasurandValues from '../../../types/MeasurandValues';
-import OCPPError from '../../../exception/OCPPError';
-import { OCPPServiceUtils } from '../OCPPServiceUtils';
-import Utils from '../../../utils/Utils';
+import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
+import Constants from '../../../utils/Constants';
+import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
 import logger from '../../../utils/Logger';
+import Utils from '../../../utils/Utils';
+import type ChargingStation from '../../ChargingStation';
+import { OCPPServiceUtils } from '../OCPPServiceUtils';
 
 export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static checkFeatureProfile(
@@ -57,12 +65,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     debug = false
   ): OCPP16MeterValue {
     const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
+      timestamp: new Date(),
       sampledValue: [],
     };
     const connector = chargingStation.getConnectorStatus(connectorId);
     // SoC measurand
-    const socSampledValueTemplate = chargingStation.getSampledValueTemplate(
+    const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
       connectorId,
       OCPP16MeterValueMeasurand.STATE_OF_CHARGE
     );
@@ -82,14 +91,15 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           `${chargingStation.logPrefix()} MeterValues measurand ${
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+          }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
             meterValue.sampledValue[sampledValuesIndex].value
           }/100`
         );
       }
     }
     // Voltage measurand
-    const voltageSampledValueTemplate = chargingStation.getSampledValueTemplate(
+    const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
       connectorId,
       OCPP16MeterValueMeasurand.VOLTAGE
     );
@@ -118,7 +128,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       ) {
         const phaseLineToNeutralValue = `L${phase}-N`;
         const voltagePhaseLineToNeutralSampledValueTemplate =
-          chargingStation.getSampledValueTemplate(
+          OCPP16ServiceUtils.getSampledValueTemplate(
+            chargingStation,
             connectorId,
             OCPP16MeterValueMeasurand.VOLTAGE,
             phaseLineToNeutralValue as OCPP16MeterValuePhase
@@ -141,7 +152,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           OCPP16ServiceUtils.buildSampledValue(
             voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
             voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
-            null,
+            undefined,
             phaseLineToNeutralValue as OCPP16MeterValuePhase
           )
         );
@@ -152,7 +163,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               : chargingStation.getNumberOfPhases()
           }`;
           const voltagePhaseLineToLineSampledValueTemplate =
-            chargingStation.getSampledValueTemplate(
+            OCPP16ServiceUtils.getSampledValueTemplate(
+              chargingStation,
               connectorId,
               OCPP16MeterValueMeasurand.VOLTAGE,
               phaseLineToLineValue as OCPP16MeterValuePhase
@@ -179,7 +191,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             OCPP16ServiceUtils.buildSampledValue(
               voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
               voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
-              null,
+              undefined,
               phaseLineToLineValue as OCPP16MeterValuePhase
             )
           );
@@ -187,24 +199,28 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       }
     }
     // Power.Active.Import measurand
-    const powerSampledValueTemplate = chargingStation.getSampledValueTemplate(
+    const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
       connectorId,
       OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
     );
     let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
     if (chargingStation.getNumberOfPhases() === 3) {
       powerPerPhaseSampledValueTemplates = {
-        L1: chargingStation.getSampledValueTemplate(
+        L1: OCPP16ServiceUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
           OCPP16MeterValuePhase.L1_N
         ),
-        L2: chargingStation.getSampledValueTemplate(
+        L2: OCPP16ServiceUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
           OCPP16MeterValuePhase.L2_N
         ),
-        L3: chargingStation.getSampledValueTemplate(
+        L3: OCPP16ServiceUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
           OCPP16MeterValuePhase.L3_N
@@ -216,7 +232,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         chargingStation,
         powerSampledValueTemplate.measurand
       );
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
+      const errMsg = `MeterValues measurand ${
         powerSampledValueTemplate.measurand ??
         OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
@@ -326,7 +342,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
           break;
         default:
-          logger.error(errMsg);
+          logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
           throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
       }
       meterValue.sampledValue.push(
@@ -346,7 +362,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           `${chargingStation.logPrefix()} MeterValues measurand ${
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+          }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
             meterValue.sampledValue[sampledValuesIndex].value
           }/${connectorMaximumPowerRounded}`
         );
@@ -362,7 +378,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             (powerPerPhaseSampledValueTemplates[`L${phase}`] as SampledValueTemplate) ??
               powerSampledValueTemplate,
             powerMeasurandValues[`L${phase}`] as number,
-            null,
+            undefined,
             phaseValue as OCPP16MeterValuePhase
           )
         );
@@ -382,7 +398,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
             }: phase ${
               meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-            }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+            }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
               meterValue.sampledValue[sampledValuesPerPhaseIndex].value
             }/${connectorMaximumPowerPerPhaseRounded}`
           );
@@ -390,24 +406,28 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       }
     }
     // Current.Import measurand
-    const currentSampledValueTemplate = chargingStation.getSampledValueTemplate(
+    const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
       connectorId,
       OCPP16MeterValueMeasurand.CURRENT_IMPORT
     );
     let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
     if (chargingStation.getNumberOfPhases() === 3) {
       currentPerPhaseSampledValueTemplates = {
-        L1: chargingStation.getSampledValueTemplate(
+        L1: OCPP16ServiceUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
           OCPP16MeterValuePhase.L1
         ),
-        L2: chargingStation.getSampledValueTemplate(
+        L2: OCPP16ServiceUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
           OCPP16MeterValuePhase.L2
         ),
-        L3: chargingStation.getSampledValueTemplate(
+        L3: OCPP16ServiceUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
           OCPP16MeterValuePhase.L3
@@ -419,7 +439,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         chargingStation,
         currentSampledValueTemplate.measurand
       );
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
+      const errMsg = `MeterValues measurand ${
         currentSampledValueTemplate.measurand ??
         OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
@@ -535,7 +555,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             : Utils.getRandomFloatRounded(connectorMaximumAmperage);
           break;
         default:
-          logger.error(errMsg);
+          logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
           throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
       }
       meterValue.sampledValue.push(
@@ -554,7 +574,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           `${chargingStation.logPrefix()} MeterValues measurand ${
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+          }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
             meterValue.sampledValue[sampledValuesIndex].value
           }/${connectorMaximumAmperage}`
         );
@@ -570,7 +590,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
               currentSampledValueTemplate,
             currentMeasurandValues[phaseValue] as number,
-            null,
+            undefined,
             phaseValue as OCPP16MeterValuePhase
           )
         );
@@ -586,7 +606,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
             }: phase ${
               meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-            }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+            }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
               meterValue.sampledValue[sampledValuesPerPhaseIndex].value
             }/${connectorMaximumAmperage}`
           );
@@ -594,7 +614,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       }
     }
     // Energy.Active.Import.Register measurand (default)
-    const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+    const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
+      connectorId
+    );
     if (energySampledValueTemplate) {
       OCPP16ServiceUtils.checkMeasurandPowerDivider(
         chargingStation,
@@ -625,9 +648,9 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       // Persist previous value on connector
       if (
         connector &&
-        !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) &&
+        Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
         connector.energyActiveImportRegisterValue >= 0 &&
-        !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) &&
+        Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
         connector.transactionEnergyActiveImportRegisterValue >= 0
       ) {
         connector.energyActiveImportRegisterValue += energyValueRounded;
@@ -653,7 +676,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
           }: connectorId ${connectorId}, transaction ${
-            connector.transactionId
+            connector?.transactionId
           }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
             interval / (3600 * 1000),
             4
@@ -670,16 +693,19 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     meterStart: number
   ): OCPP16MeterValue {
     const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
+      timestamp: new Date(),
       sampledValue: [],
     };
     // Energy.Active.Import.Register measurand (default)
-    const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+    const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
+      connectorId
+    );
     const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
         sampledValueTemplate,
-        Utils.roundTo(meterStart / unitDivider, 4),
+        Utils.roundTo((meterStart ?? 0) / unitDivider, 4),
         MeterValueContext.TRANSACTION_BEGIN
       )
     );
@@ -692,16 +718,19 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     meterStop: number
   ): OCPP16MeterValue {
     const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
+      timestamp: new Date(),
       sampledValue: [],
     };
     // Energy.Active.Import.Register measurand (default)
-    const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+    const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
+      connectorId
+    );
     const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
         sampledValueTemplate,
-        Utils.roundTo(meterStop / unitDivider, 4),
+        Utils.roundTo((meterStop ?? 0) / unitDivider, 4),
         MeterValueContext.TRANSACTION_END
       )
     );
@@ -718,6 +747,58 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return meterValues;
   }
 
+  public static setChargingProfile(
+    chargingStation: ChargingStation,
+    connectorId: number,
+    cp: OCPP16ChargingProfile
+  ): void {
+    if (
+      Utils.isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)
+    ) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
+      );
+      chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
+    }
+    if (
+      Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
+    ) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
+      );
+      chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
+    }
+    let cpReplaced = false;
+    if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
+      chargingStation
+        .getConnectorStatus(connectorId)
+        ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
+          if (
+            chargingProfile.chargingProfileId === cp.chargingProfileId ||
+            (chargingProfile.stackLevel === cp.stackLevel &&
+              chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
+          ) {
+            chargingStation.getConnectorStatus(connectorId).chargingProfiles[index] = cp;
+            cpReplaced = true;
+          }
+        });
+    }
+    !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
+  }
+
+  public static parseJsonSchemaFile<T extends JsonType>(
+    relativePath: string,
+    moduleName?: string,
+    methodName?: string
+  ): JSONSchemaType<T> {
+    return super.parseJsonSchemaFile<T>(
+      path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
+      OCPPVersion.VERSION_16,
+      moduleName,
+      methodName
+    );
+  }
+
   private static buildSampledValue(
     sampledValueTemplate: SampledValueTemplate,
     value: number,
@@ -748,17 +829,17 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     chargingStation: ChargingStation,
     measurandType: OCPP16MeterValueMeasurand
   ): void {
-    if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) {
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
+    if (Utils.isUndefined(chargingStation.powerDivider)) {
+      const errMsg = `MeterValues measurand ${
         measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       }: powerDivider is undefined`;
-      logger.error(errMsg);
+      logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
       throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-    } else if (chargingStation.stationInfo?.powerDivider <= 0) {
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
+    } else if (chargingStation?.powerDivider <= 0) {
+      const errMsg = `MeterValues measurand ${
         measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
-      logger.error(errMsg);
+      }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
+      logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
       throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
     }
   }