refactor: factor out change availability helper
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
index 089ecc76e065d4eb01d3ca36da3a71a571e7147c..cf055beb949d05f5844c86a450fd6cfc27c7f8ee 100644 (file)
@@ -1,49 +1,64 @@
 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
-import OCPPError from '../../../exception/OCPPError';
-import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
-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 type { JSONSchemaType } from 'ajv';
+
+import { OCPP16Constants } from './OCPP16Constants';
+import { type ChargingStation, hasFeatureProfile } from '../../../charging-station';
+import { OCPPError } from '../../../exception';
 import {
+  type ClearChargingProfileRequest,
+  CurrentType,
+  ErrorType,
+  type JsonType,
+  type MeasurandPerPhaseSampledValueTemplates,
+  type MeasurandValues,
   MeterValueContext,
   MeterValueLocation,
   MeterValueUnit,
+  OCPP16AvailabilityType,
+  type OCPP16ChangeAvailabilityResponse,
+  OCPP16ChargePointStatus,
+  type OCPP16ChargingProfile,
+  type OCPP16IncomingRequestCommand,
   type OCPP16MeterValue,
   OCPP16MeterValueMeasurand,
   OCPP16MeterValuePhase,
+  OCPP16RequestCommand,
   type OCPP16SampledValue,
-} from '../../../types/ocpp/1.6/MeterValues';
+  OCPP16StandardParametersKey,
+  type OCPP16SupportedFeatureProfiles,
+  OCPPVersion,
+  type SampledValueTemplate,
+  Voltage,
+} from '../../../types';
 import {
-  type OCPP16IncomingRequestCommand,
-  OCPP16RequestCommand,
-} from '../../../types/ocpp/1.6/Requests';
-import { ErrorType } from '../../../types/ocpp/ErrorType';
-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';
+  ACElectricUtils,
+  Constants,
+  DCElectricUtils,
+  convertToFloat,
+  convertToInt,
+  getRandomFloatFluctuatedRounded,
+  getRandomFloatRounded,
+  getRandomInteger,
+  isNotEmptyArray,
+  isNullOrUndefined,
+  isUndefined,
+  logger,
+  roundTo,
+} from '../../../utils';
 import { OCPPServiceUtils } from '../OCPPServiceUtils';
 
 export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static checkFeatureProfile(
     chargingStation: ChargingStation,
     featureProfile: OCPP16SupportedFeatureProfiles,
-    command: OCPP16RequestCommand | OCPP16IncomingRequestCommand
+    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
-        } in configuration`
+        } in configuration`,
       );
       return false;
     }
@@ -55,10 +70,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     connectorId: number,
     transactionId: number,
     interval: number,
-    debug = false
+    debug = false,
   ): OCPP16MeterValue {
     const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
+      timestamp: new Date(),
       sampledValue: [],
     };
     const connector = chargingStation.getConnectorStatus(connectorId);
@@ -66,27 +81,33 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
       chargingStation,
       connectorId,
-      OCPP16MeterValueMeasurand.STATE_OF_CHARGE
+      OCPP16MeterValueMeasurand.STATE_OF_CHARGE,
     );
     if (socSampledValueTemplate) {
+      const socMaximumValue = 100;
+      const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0;
       const socSampledValueTemplateValue = socSampledValueTemplate.value
-        ? Utils.getRandomFloatFluctuatedRounded(
+        ? getRandomFloatFluctuatedRounded(
             parseInt(socSampledValueTemplate.value),
-            socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+            socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
           )
-        : Utils.getRandomInteger(100);
+        : getRandomInteger(socMaximumValue, socMinimumValue);
       meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue)
+        OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue),
       );
       const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
+      if (
+        convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue ||
+        convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue ||
+        debug
+      ) {
         logger.error(
           `${chargingStation.logPrefix()} MeterValues measurand ${
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
             meterValue.sampledValue[sampledValuesIndex].value
-          }/100`
+          }/${socMaximumValue}}`,
         );
       }
     }
@@ -94,7 +115,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
       chargingStation,
       connectorId,
-      OCPP16MeterValueMeasurand.VOLTAGE
+      OCPP16MeterValueMeasurand.VOLTAGE,
     );
     if (voltageSampledValueTemplate) {
       const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value
@@ -102,16 +123,16 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         : chargingStation.getVoltageOut();
       const fluctuationPercent =
         voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT;
-      const voltageMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
+      const voltageMeasurandValue = getRandomFloatFluctuatedRounded(
         voltageSampledValueTemplateValue,
-        fluctuationPercent
+        fluctuationPercent,
       );
       if (
         chargingStation.getNumberOfPhases() !== 3 ||
         (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues())
       ) {
         meterValue.sampledValue.push(
-          OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue)
+          OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue),
         );
       }
       for (
@@ -125,9 +146,9 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             chargingStation,
             connectorId,
             OCPP16MeterValueMeasurand.VOLTAGE,
-            phaseLineToNeutralValue as OCPP16MeterValuePhase
+            phaseLineToNeutralValue as OCPP16MeterValuePhase,
           );
-        let voltagePhaseLineToNeutralMeasurandValue: number;
+        let voltagePhaseLineToNeutralMeasurandValue: number | undefined;
         if (voltagePhaseLineToNeutralSampledValueTemplate) {
           const voltagePhaseLineToNeutralSampledValueTemplateValue =
             voltagePhaseLineToNeutralSampledValueTemplate.value
@@ -136,18 +157,18 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           const fluctuationPhaseToNeutralPercent =
             voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
             Constants.DEFAULT_FLUCTUATION_PERCENT;
-          voltagePhaseLineToNeutralMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
+          voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded(
             voltagePhaseLineToNeutralSampledValueTemplateValue,
-            fluctuationPhaseToNeutralPercent
+            fluctuationPhaseToNeutralPercent,
           );
         }
         meterValue.sampledValue.push(
           OCPP16ServiceUtils.buildSampledValue(
             voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
             voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
-            null,
-            phaseLineToNeutralValue as OCPP16MeterValuePhase
-          )
+            undefined,
+            phaseLineToNeutralValue as OCPP16MeterValuePhase,
+          ),
         );
         if (chargingStation.getPhaseLineToLineVoltageMeterValues()) {
           const phaseLineToLineValue = `L${phase}-L${
@@ -160,9 +181,9 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               chargingStation,
               connectorId,
               OCPP16MeterValueMeasurand.VOLTAGE,
-              phaseLineToLineValue as OCPP16MeterValuePhase
+              phaseLineToLineValue as OCPP16MeterValuePhase,
             );
-          let voltagePhaseLineToLineMeasurandValue: number;
+          let voltagePhaseLineToLineMeasurandValue: number | undefined;
           if (voltagePhaseLineToLineSampledValueTemplate) {
             const voltagePhaseLineToLineSampledValueTemplateValue =
               voltagePhaseLineToLineSampledValueTemplate.value
@@ -171,22 +192,22 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             const fluctuationPhaseLineToLinePercent =
               voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
               Constants.DEFAULT_FLUCTUATION_PERCENT;
-            voltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
+            voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
               voltagePhaseLineToLineSampledValueTemplateValue,
-              fluctuationPhaseLineToLinePercent
+              fluctuationPhaseLineToLinePercent,
             );
           }
-          const defaultVoltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
+          const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
             Voltage.VOLTAGE_400,
-            fluctuationPercent
+            fluctuationPercent,
           );
           meterValue.sampledValue.push(
             OCPP16ServiceUtils.buildSampledValue(
               voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
               voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
-              null,
-              phaseLineToLineValue as OCPP16MeterValuePhase
-            )
+              undefined,
+              phaseLineToLineValue as OCPP16MeterValuePhase,
+            ),
           );
         }
       }
@@ -195,7 +216,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
       chargingStation,
       connectorId,
-      OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
+      OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
     );
     let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
     if (chargingStation.getNumberOfPhases() === 3) {
@@ -204,26 +225,26 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L1_N
+          OCPP16MeterValuePhase.L1_N,
         ),
         L2: OCPP16ServiceUtils.getSampledValueTemplate(
           chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L2_N
+          OCPP16MeterValuePhase.L2_N,
         ),
         L3: OCPP16ServiceUtils.getSampledValueTemplate(
           chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L3_N
+          OCPP16MeterValuePhase.L3_N,
         ),
       };
     }
     if (powerSampledValueTemplate) {
       OCPP16ServiceUtils.checkMeasurandPowerDivider(
         chargingStation,
-        powerSampledValueTemplate.measurand
+        powerSampledValueTemplate.measurand!,
       );
       const errMsg = `MeterValues measurand ${
         powerSampledValueTemplate.measurand ??
@@ -234,105 +255,124 @@ 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);
       const connectorMaximumPower = Math.round(connectorMaximumAvailablePower);
       const connectorMaximumPowerPerPhase = Math.round(
-        connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
+        connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(),
+      );
+      const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue!) ?? 0;
+      const connectorMinimumPowerPerPhase = Math.round(
+        connectorMinimumPower / chargingStation.getNumberOfPhases(),
       );
       switch (chargingStation.getCurrentOutType()) {
         case CurrentType.AC:
           if (chargingStation.getNumberOfPhases() === 3) {
             const defaultFluctuatedPowerPerPhase =
               powerSampledValueTemplate.value &&
-              Utils.getRandomFloatFluctuatedRounded(
+              getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerSampledValueTemplate.value,
                   connectorMaximumPower / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ) / chargingStation.getNumberOfPhases(),
                 powerSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase1FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L1?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
+              powerPerPhaseSampledValueTemplates.L1?.value &&
+              getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L1.value,
                   connectorMaximumPowerPerPhase / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase2FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L2?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
+              powerPerPhaseSampledValueTemplates.L2?.value &&
+              getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L2.value,
                   connectorMaximumPowerPerPhase / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase3FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L3?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
+              powerPerPhaseSampledValueTemplates.L3?.value &&
+              getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L3.value,
                   connectorMaximumPowerPerPhase / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             powerMeasurandValues.L1 =
-              phase1FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
-              Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
+              (phase1FluctuatedValue as number) ??
+              (defaultFluctuatedPowerPerPhase as number) ??
+              getRandomFloatRounded(
+                connectorMaximumPowerPerPhase / unitDivider,
+                connectorMinimumPowerPerPhase / unitDivider,
+              );
             powerMeasurandValues.L2 =
-              phase2FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
-              Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
+              (phase2FluctuatedValue as number) ??
+              (defaultFluctuatedPowerPerPhase as number) ??
+              getRandomFloatRounded(
+                connectorMaximumPowerPerPhase / unitDivider,
+                connectorMinimumPowerPerPhase / unitDivider,
+              );
             powerMeasurandValues.L3 =
-              phase3FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
-              Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
+              (phase3FluctuatedValue as number) ??
+              (defaultFluctuatedPowerPerPhase as number) ??
+              getRandomFloatRounded(
+                connectorMaximumPowerPerPhase / unitDivider,
+                connectorMinimumPowerPerPhase / unitDivider,
+              );
           } else {
             powerMeasurandValues.L1 = powerSampledValueTemplate.value
-              ? Utils.getRandomFloatFluctuatedRounded(
+              ? getRandomFloatFluctuatedRounded(
                   OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                     powerSampledValueTemplate.value,
                     connectorMaximumPower / unitDivider,
-                    { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                    { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                   ),
                   powerSampledValueTemplate.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
                 )
-              : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
+              : getRandomFloatRounded(
+                  connectorMaximumPower / unitDivider,
+                  connectorMinimumPower / unitDivider,
+                );
             powerMeasurandValues.L2 = 0;
             powerMeasurandValues.L3 = 0;
           }
-          powerMeasurandValues.allPhases = Utils.roundTo(
+          powerMeasurandValues.allPhases = roundTo(
             powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
-            2
+            2,
           );
           break;
         case CurrentType.DC:
           powerMeasurandValues.allPhases = powerSampledValueTemplate.value
-            ? Utils.getRandomFloatFluctuatedRounded(
+            ? getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerSampledValueTemplate.value,
                   connectorMaximumPower / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 powerSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               )
-            : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
+            : getRandomFloatRounded(
+                connectorMaximumPower / unitDivider,
+                connectorMinimumPower / unitDivider,
+              );
           break;
         default:
           logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
@@ -341,23 +381,26 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       meterValue.sampledValue.push(
         OCPP16ServiceUtils.buildSampledValue(
           powerSampledValueTemplate,
-          powerMeasurandValues.allPhases
-        )
+          powerMeasurandValues.allPhases,
+        ),
       );
       const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2);
+      const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2);
+      const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2);
       if (
-        Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
+        convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
           connectorMaximumPowerRounded ||
+        convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
+          connectorMinimumPowerRounded ||
         debug
       ) {
         logger.error(
           `${chargingStation.logPrefix()} MeterValues measurand ${
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
             meterValue.sampledValue[sampledValuesIndex].value
-          }/${connectorMaximumPowerRounded}`
+          }/${connectorMaximumPowerRounded}`,
         );
       }
       for (
@@ -368,21 +411,28 @@ 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,
-            null,
-            phaseValue as OCPP16MeterValuePhase
-          )
+            powerPerPhaseSampledValueTemplates[
+              `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
+            ]! ?? powerSampledValueTemplate,
+            powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
+            undefined,
+            phaseValue as OCPP16MeterValuePhase,
+          ),
         );
         const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
-        const connectorMaximumPowerPerPhaseRounded = Utils.roundTo(
+        const connectorMaximumPowerPerPhaseRounded = roundTo(
           connectorMaximumPowerPerPhase / unitDivider,
-          2
+          2,
+        );
+        const connectorMinimumPowerPerPhaseRounded = roundTo(
+          connectorMinimumPowerPerPhase / unitDivider,
+          2,
         );
         if (
-          Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
+          convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
             connectorMaximumPowerPerPhaseRounded ||
+          convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
+            connectorMinimumPowerPerPhaseRounded ||
           debug
         ) {
           logger.error(
@@ -391,9 +441,9 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
             }: phase ${
               meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-            }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+            }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
               meterValue.sampledValue[sampledValuesPerPhaseIndex].value
-            }/${connectorMaximumPowerPerPhaseRounded}`
+            }/${connectorMaximumPowerPerPhaseRounded}`,
           );
         }
       }
@@ -402,7 +452,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
       chargingStation,
       connectorId,
-      OCPP16MeterValueMeasurand.CURRENT_IMPORT
+      OCPP16MeterValueMeasurand.CURRENT_IMPORT,
     );
     let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
     if (chargingStation.getNumberOfPhases() === 3) {
@@ -411,26 +461,26 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L1
+          OCPP16MeterValuePhase.L1,
         ),
         L2: OCPP16ServiceUtils.getSampledValueTemplate(
           chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L2
+          OCPP16MeterValuePhase.L2,
         ),
         L3: OCPP16ServiceUtils.getSampledValueTemplate(
           chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L3
+          OCPP16MeterValuePhase.L3,
         ),
       };
     }
     if (currentSampledValueTemplate) {
       OCPP16ServiceUtils.checkMeasurandPowerDivider(
         chargingStation,
-        currentSampledValueTemplate.measurand
+        currentSampledValueTemplate.measurand!,
       );
       const errMsg = `MeterValues measurand ${
         currentSampledValueTemplate.measurand ??
@@ -444,108 +494,109 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
       const connectorMaximumAvailablePower =
         chargingStation.getConnectorMaximumAvailablePower(connectorId);
+      const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
       let connectorMaximumAmperage: number;
       switch (chargingStation.getCurrentOutType()) {
         case CurrentType.AC:
           connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
             chargingStation.getNumberOfPhases(),
             connectorMaximumAvailablePower,
-            chargingStation.getVoltageOut()
+            chargingStation.getVoltageOut(),
           );
           if (chargingStation.getNumberOfPhases() === 3) {
             const defaultFluctuatedAmperagePerPhase =
               currentSampledValueTemplate.value &&
-              Utils.getRandomFloatFluctuatedRounded(
+              getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentSampledValueTemplate.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 currentSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase1FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L1?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
+              currentPerPhaseSampledValueTemplates.L1?.value &&
+              getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L1.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase2FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L2?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
+              currentPerPhaseSampledValueTemplates.L2?.value &&
+              getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L2.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             const phase3FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L3?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
+              currentPerPhaseSampledValueTemplates.L3?.value &&
+              getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L3.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               );
             currentMeasurandValues.L1 =
-              phase1FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
-              Utils.getRandomFloatRounded(connectorMaximumAmperage);
+              (phase1FluctuatedValue as number) ??
+              (defaultFluctuatedAmperagePerPhase as number) ??
+              getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
             currentMeasurandValues.L2 =
-              phase2FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
-              Utils.getRandomFloatRounded(connectorMaximumAmperage);
+              (phase2FluctuatedValue as number) ??
+              (defaultFluctuatedAmperagePerPhase as number) ??
+              getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
             currentMeasurandValues.L3 =
-              phase3FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
-              Utils.getRandomFloatRounded(connectorMaximumAmperage);
+              (phase3FluctuatedValue as number) ??
+              (defaultFluctuatedAmperagePerPhase as number) ??
+              getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
           } else {
             currentMeasurandValues.L1 = currentSampledValueTemplate.value
-              ? Utils.getRandomFloatFluctuatedRounded(
+              ? getRandomFloatFluctuatedRounded(
                   OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                     currentSampledValueTemplate.value,
                     connectorMaximumAmperage,
-                    { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                    { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                   ),
                   currentSampledValueTemplate.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
                 )
-              : Utils.getRandomFloatRounded(connectorMaximumAmperage);
+              : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
             currentMeasurandValues.L2 = 0;
             currentMeasurandValues.L3 = 0;
           }
-          currentMeasurandValues.allPhases = Utils.roundTo(
+          currentMeasurandValues.allPhases = roundTo(
             (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
               chargingStation.getNumberOfPhases(),
-            2
+            2,
           );
           break;
         case CurrentType.DC:
           connectorMaximumAmperage = DCElectricUtils.amperage(
             connectorMaximumAvailablePower,
-            chargingStation.getVoltageOut()
+            chargingStation.getVoltageOut(),
           );
           currentMeasurandValues.allPhases = currentSampledValueTemplate.value
-            ? Utils.getRandomFloatFluctuatedRounded(
+            ? getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentSampledValueTemplate.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
                 ),
                 currentSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+                  Constants.DEFAULT_FLUCTUATION_PERCENT,
               )
-            : Utils.getRandomFloatRounded(connectorMaximumAmperage);
+            : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
           break;
         default:
           logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
@@ -554,22 +605,24 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       meterValue.sampledValue.push(
         OCPP16ServiceUtils.buildSampledValue(
           currentSampledValueTemplate,
-          currentMeasurandValues.allPhases
-        )
+          currentMeasurandValues.allPhases,
+        ),
       );
       const sampledValuesIndex = meterValue.sampledValue.length - 1;
       if (
-        Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
+        convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
           connectorMaximumAmperage ||
+        convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
+          connectorMinimumAmperage ||
         debug
       ) {
         logger.error(
           `${chargingStation.logPrefix()} MeterValues measurand ${
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
             meterValue.sampledValue[sampledValuesIndex].value
-          }/${connectorMaximumAmperage}`
+          }/${connectorMaximumAmperage}`,
         );
       }
       for (
@@ -580,17 +633,20 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         const phaseValue = `L${phase}`;
         meterValue.sampledValue.push(
           OCPP16ServiceUtils.buildSampledValue(
-            (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
-              currentSampledValueTemplate,
-            currentMeasurandValues[phaseValue] as number,
-            null,
-            phaseValue as OCPP16MeterValuePhase
-          )
+            currentPerPhaseSampledValueTemplates[
+              phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
+            ]! ?? currentSampledValueTemplate,
+            currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
+            undefined,
+            phaseValue as OCPP16MeterValuePhase,
+          ),
         );
         const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
         if (
-          Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
+          convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
             connectorMaximumAmperage ||
+          convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
+            connectorMinimumAmperage ||
           debug
         ) {
           logger.error(
@@ -599,9 +655,9 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
             }: phase ${
               meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-            }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+            }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
               meterValue.sampledValue[sampledValuesPerPhaseIndex].value
-            }/${connectorMaximumAmperage}`
+            }/${connectorMaximumAmperage}`,
           );
         }
       }
@@ -609,58 +665,59 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     // Energy.Active.Import.Register measurand (default)
     const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
       chargingStation,
-      connectorId
+      connectorId,
     );
     if (energySampledValueTemplate) {
       OCPP16ServiceUtils.checkMeasurandPowerDivider(
         chargingStation,
-        energySampledValueTemplate.measurand
+        energySampledValueTemplate.measurand!,
       );
       const unitDivider =
         energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
       const connectorMaximumAvailablePower =
         chargingStation.getConnectorMaximumAvailablePower(connectorId);
-      const connectorMaximumEnergyRounded = Utils.roundTo(
+      const connectorMaximumEnergyRounded = roundTo(
         (connectorMaximumAvailablePower * interval) / (3600 * 1000),
-        2
+        2,
       );
       const energyValueRounded = energySampledValueTemplate.value
         ? // Cumulate the fluctuated value around the static one
-          Utils.getRandomFloatFluctuatedRounded(
+          getRandomFloatFluctuatedRounded(
             OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
               energySampledValueTemplate.value,
               connectorMaximumEnergyRounded,
               {
                 limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(),
                 unitMultiplier: unitDivider,
-              }
+              },
             ),
-            energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+            energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
           )
-        : Utils.getRandomFloatRounded(connectorMaximumEnergyRounded);
+        : getRandomFloatRounded(connectorMaximumEnergyRounded);
       // Persist previous value on connector
-      if (
-        connector &&
-        Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
-        connector.energyActiveImportRegisterValue >= 0 &&
-        Utils.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(
           energySampledValueTemplate,
-          Utils.roundTo(
+          roundTo(
             chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
               unitDivider,
-            2
-          )
-        )
+            2,
+          ),
+        ),
       );
       const sampledValuesIndex = meterValue.sampledValue.length - 1;
       if (energyValueRounded > connectorMaximumEnergyRounded || debug) {
@@ -668,12 +725,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: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
-            interval / (3600 * 1000),
-            4
-          )}h`
+          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
         );
       }
     }
@@ -683,24 +735,24 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static buildTransactionBeginMeterValue(
     chargingStation: ChargingStation,
     connectorId: number,
-    meterStart: number
+    meterStart: number,
   ): OCPP16MeterValue {
     const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
+      timestamp: new Date(),
       sampledValue: [],
     };
     // Energy.Active.Import.Register measurand (default)
     const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
       chargingStation,
-      connectorId
+      connectorId,
     );
     const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
-        sampledValueTemplate,
-        Utils.roundTo((meterStart ?? 0) / unitDivider, 4),
-        MeterValueContext.TRANSACTION_BEGIN
-      )
+        sampledValueTemplate!,
+        roundTo((meterStart ?? 0) / unitDivider, 4),
+        MeterValueContext.TRANSACTION_BEGIN,
+      ),
     );
     return meterValue;
   }
@@ -708,31 +760,31 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static buildTransactionEndMeterValue(
     chargingStation: ChargingStation,
     connectorId: number,
-    meterStop: number
+    meterStop: number,
   ): OCPP16MeterValue {
     const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
+      timestamp: new Date(),
       sampledValue: [],
     };
     // Energy.Active.Import.Register measurand (default)
     const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
       chargingStation,
-      connectorId
+      connectorId,
     );
     const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
-        sampledValueTemplate,
-        Utils.roundTo((meterStop ?? 0) / unitDivider, 4),
-        MeterValueContext.TRANSACTION_END
-      )
+        sampledValueTemplate!,
+        roundTo((meterStop ?? 0) / unitDivider, 4),
+        MeterValueContext.TRANSACTION_END,
+      ),
     );
     return meterValue;
   }
 
   public static buildTransactionDataMeterValues(
     transactionBeginMeterValue: OCPP16MeterValue,
-    transactionEndMeterValue: OCPP16MeterValue
+    transactionEndMeterValue: OCPP16MeterValue,
   ): OCPP16MeterValue[] {
     const meterValues: OCPP16MeterValue[] = [];
     meterValues.push(transactionBeginMeterValue);
@@ -740,72 +792,153 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return meterValues;
   }
 
+  public static changeAvailability = async (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    chargePointStatus: OCPP16ChargePointStatus,
+    availabilityType: OCPP16AvailabilityType,
+  ): Promise<OCPP16ChangeAvailabilityResponse> => {
+    let response: OCPP16ChangeAvailabilityResponse =
+      OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
+    if (connectorStatus?.transactionStarted === true) {
+      response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
+    }
+    connectorStatus.availability = availabilityType;
+    if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
+      await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+        chargingStation,
+        connectorId,
+        chargePointStatus,
+      );
+    }
+    return response;
+  };
+
   public static setChargingProfile(
     chargingStation: ChargingStation,
     connectorId: number,
-    cp: OCPP16ChargingProfile
+    cp: OCPP16ChargingProfile,
   ): void {
-    if (Utils.isNullOrUndefined(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
+    if (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.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) {
+    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.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 (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
+    if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
       chargingStation
         .getConnectorStatus(connectorId)
-        .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
+        ?.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;
+            chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp;
             cpReplaced = true;
           }
         });
     }
-    !cpReplaced && chargingStation.getConnectorStatus(connectorId).chargingProfiles?.push(cp);
+    !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,
+    methodName?: string,
+  ): JSONSchemaType<T> {
+    return super.parseJsonSchemaFile<T>(
+      relativePath,
+      OCPPVersion.VERSION_16,
+      moduleName,
+      methodName,
+    );
   }
 
   private static buildSampledValue(
     sampledValueTemplate: SampledValueTemplate,
     value: number,
     context?: MeterValueContext,
-    phase?: OCPP16MeterValuePhase
+    phase?: OCPP16MeterValuePhase,
   ): OCPP16SampledValue {
     const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
     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 {
-      ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && {
+      ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
         unit: sampledValueTemplate.unit,
       }),
-      ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
-      ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && {
+      ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
+      ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
         measurand: sampledValueTemplate.measurand,
       }),
-      ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
-      ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
-      ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
-    };
+      ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
+      ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
+      ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
+    } as OCPP16SampledValue;
   }
 
   private static checkMeasurandPowerDivider(
     chargingStation: ChargingStation,
-    measurandType: OCPP16MeterValueMeasurand
+    measurandType: OCPP16MeterValueMeasurand,
   ): void {
-    if (Utils.isUndefined(chargingStation.powerDivider)) {
+    if (isUndefined(chargingStation.powerDivider)) {
       const errMsg = `MeterValues measurand ${
         measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       }: powerDivider is undefined`;
@@ -821,7 +954,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   }
 
   private static getMeasurandDefaultLocation(
-    measurandType: OCPP16MeterValueMeasurand
+    measurandType: OCPP16MeterValueMeasurand,
   ): MeterValueLocation | undefined {
     switch (measurandType) {
       case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
@@ -830,7 +963,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   }
 
   private static getMeasurandDefaultUnit(
-    measurandType: OCPP16MeterValueMeasurand
+    measurandType: OCPP16MeterValueMeasurand,
   ): MeterValueUnit | undefined {
     switch (measurandType) {
       case OCPP16MeterValueMeasurand.CURRENT_EXPORT: