refactor: remove unneeded eslint-disable
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
index 64b49c46d4f2f165ec320d56fcfead811babf048..1ffd6f5efc5af45114a0a311285e0c9aa4e328ce 100644 (file)
-// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
-import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
-import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
-import MeasurandPerPhaseSampledValueTemplates, {
-  SampledValueTemplate,
-} from '../../../types/MeasurandPerPhaseSampledValueTemplates';
+import type { JSONSchemaType } from 'ajv'
 import {
-  MeterValueContext,
-  MeterValueLocation,
-  MeterValueUnit,
-  OCPP16MeterValue,
-  OCPP16MeterValueMeasurand,
-  OCPP16MeterValuePhase,
-  OCPP16SampledValue,
-} from '../../../types/ocpp/1.6/MeterValues';
+  type Interval,
+  addSeconds,
+  areIntervalsOverlapping,
+  differenceInSeconds,
+  isAfter,
+  isBefore,
+  isWithinInterval
+} from 'date-fns'
 
-import type ChargingStation from '../../ChargingStation';
-import Constants from '../../../utils/Constants';
-import { ErrorType } from '../../../types/ocpp/ErrorType';
-import MeasurandValues from '../../../types/MeasurandValues';
-import { OCPP16RequestCommand } from '../../../types/ocpp/1.6/Requests';
-import OCPPError from '../../../exception/OCPPError';
-import Utils from '../../../utils/Utils';
-import logger from '../../../utils/Logger';
+import { OCPP16Constants } from './OCPP16Constants.js'
+import {
+  type ChargingStation,
+  hasFeatureProfile,
+  hasReservationExpired
+} from '../../../charging-station/index.js'
+import {
+  type ConfigurationKey,
+  type GenericResponse,
+  type JsonType,
+  OCPP16AuthorizationStatus,
+  type OCPP16AvailabilityType,
+  type OCPP16ChangeAvailabilityResponse,
+  OCPP16ChargePointStatus,
+  type OCPP16ChargingProfile,
+  type OCPP16ChargingSchedule,
+  type OCPP16ClearChargingProfileRequest,
+  type OCPP16IncomingRequestCommand,
+  type OCPP16MeterValue,
+  OCPP16MeterValueContext,
+  OCPP16MeterValueUnit,
+  type OCPP16RequestCommand,
+  OCPP16StandardParametersKey,
+  OCPP16StopTransactionReason,
+  type OCPP16SupportedFeatureProfiles,
+  OCPPVersion
+} from '../../../types/index.js'
+import { convertToDate, isNotEmptyArray, logger, roundTo } from '../../../utils/index.js'
+import { OCPPServiceUtils } from '../OCPPServiceUtils.js'
 
-export class OCPP16ServiceUtils {
-  public static checkMeasurandPowerDivider(
+export class OCPP16ServiceUtils extends OCPPServiceUtils {
+  public static checkFeatureProfile (
     chargingStation: ChargingStation,
-    measurandType: OCPP16MeterValueMeasurand
-  ): void {
-    if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) {
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
-        measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: powerDivider is undefined`;
-      logger.error(errMsg);
-      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-    } else if (chargingStation.stationInfo?.powerDivider <= 0) {
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
-        measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
-      logger.error(errMsg);
-      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
+    featureProfile: OCPP16SupportedFeatureProfiles,
+    command: OCPP16RequestCommand | OCPP16IncomingRequestCommand
+  ): boolean {
+    if (hasFeatureProfile(chargingStation, featureProfile) === false) {
+      logger.warn(
+        `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
+          OCPP16StandardParametersKey.SupportedFeatureProfiles
+        } in configuration`
+      )
+      return false
     }
+    return true
   }
 
-  public static buildSampledValue(
-    sampledValueTemplate: SampledValueTemplate,
-    value: number,
-    context?: MeterValueContext,
-    phase?: OCPP16MeterValuePhase
-  ): OCPP16SampledValue {
-    const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
-    const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
-    const sampledValueLocation =
-      sampledValueTemplate?.location ??
-      OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null);
-    const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
-    return {
-      ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && {
-        unit: sampledValueTemplate.unit,
-      }),
-      ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
-      ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && {
-        measurand: sampledValueTemplate.measurand,
-      }),
-      ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
-      ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
-      ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
-    };
+  public static buildTransactionBeginMeterValue (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    meterStart: number | undefined
+  ): OCPP16MeterValue {
+    const meterValue: OCPP16MeterValue = {
+      timestamp: new Date(),
+      sampledValue: []
+    }
+    // Energy.Active.Import.Register measurand (default)
+    const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
+      connectorId
+    )
+    const unitDivider =
+      sampledValueTemplate?.unit === OCPP16MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
+    meterValue.sampledValue.push(
+      OCPP16ServiceUtils.buildSampledValue(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        sampledValueTemplate!,
+        roundTo((meterStart ?? 0) / unitDivider, 4),
+        OCPP16MeterValueContext.TRANSACTION_BEGIN
+      )
+    )
+    return meterValue
+  }
+
+  public static buildTransactionDataMeterValues (
+    transactionBeginMeterValue: OCPP16MeterValue,
+    transactionEndMeterValue: OCPP16MeterValue
+  ): OCPP16MeterValue[] {
+    const meterValues: OCPP16MeterValue[] = []
+    meterValues.push(transactionBeginMeterValue)
+    meterValues.push(transactionEndMeterValue)
+    return meterValues
   }
 
-  public static getMeasurandDefaultUnit(
-    measurandType: OCPP16MeterValueMeasurand
-  ): MeterValueUnit | undefined {
-    switch (measurandType) {
-      case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
-      case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
-      case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
-        return MeterValueUnit.AMP;
-      case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
-      case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
-        return MeterValueUnit.WATT_HOUR;
-      case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
-      case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
-      case OCPP16MeterValueMeasurand.POWER_OFFERED:
-        return MeterValueUnit.WATT;
-      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
-        return MeterValueUnit.PERCENT;
-      case OCPP16MeterValueMeasurand.VOLTAGE:
-        return MeterValueUnit.VOLT;
+  public static remoteStopTransaction = async (
+    chargingStation: ChargingStation,
+    connectorId: number
+  ): Promise<GenericResponse> => {
+    await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+      chargingStation,
+      connectorId,
+      OCPP16ChargePointStatus.Finishing
+    )
+    const stopResponse = await chargingStation.stopTransactionOnConnector(
+      connectorId,
+      OCPP16StopTransactionReason.REMOTE
+    )
+    if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
+      return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
     }
+    return OCPP16Constants.OCPP_RESPONSE_REJECTED
   }
 
-  public static getMeasurandDefaultLocation(
-    measurandType: OCPP16MeterValueMeasurand
-  ): MeterValueLocation | undefined {
-    switch (measurandType) {
-      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
-        return MeterValueLocation.EV;
+  public static changeAvailability = async (
+    chargingStation: ChargingStation,
+    connectorIds: number[],
+    chargePointStatus: OCPP16ChargePointStatus,
+    availabilityType: OCPP16AvailabilityType
+  ): Promise<OCPP16ChangeAvailabilityResponse> => {
+    const responses: OCPP16ChangeAvailabilityResponse[] = []
+    for (const connectorId of connectorIds) {
+      let response: OCPP16ChangeAvailabilityResponse =
+        OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      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
+        )
+      }
+      responses.push(response)
+    }
+    if (responses.includes(OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED)) {
+      return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
     }
+    return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
   }
 
-  public static buildMeterValue(
+  public static setChargingProfile (
     chargingStation: ChargingStation,
     connectorId: number,
-    transactionId: number,
-    interval: number,
-    debug = false
-  ): OCPP16MeterValue {
-    const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
-      sampledValue: [],
-    };
-    const connector = chargingStation.getConnectorStatus(connectorId);
-    // SoC measurand
-    const socSampledValueTemplate = chargingStation.getSampledValueTemplate(
-      connectorId,
-      OCPP16MeterValueMeasurand.STATE_OF_CHARGE
-    );
-    if (socSampledValueTemplate) {
-      const socSampledValueTemplateValue = socSampledValueTemplate.value
-        ? Utils.getRandomFloatFluctuatedRounded(
-            parseInt(socSampledValueTemplate.value),
-            socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
-          )
-        : Utils.getRandomInteger(100);
-      meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue)
-      );
-      const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
-        logger.error(
-          `${chargingStation.logPrefix()} MeterValues measurand ${
-            meterValue.sampledValue[sampledValuesIndex].measurand ??
-            OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
-            meterValue.sampledValue[sampledValuesIndex].value
-          }/100`
-        );
-      }
+    cp: OCPP16ChargingProfile
+  ): void {
+    if (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles == null) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
+      )
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []
     }
-    // Voltage measurand
-    const voltageSampledValueTemplate = chargingStation.getSampledValueTemplate(
-      connectorId,
-      OCPP16MeterValueMeasurand.VOLTAGE
-    );
-    if (voltageSampledValueTemplate) {
-      const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value
-        ? parseInt(voltageSampledValueTemplate.value)
-        : chargingStation.getVoltageOut();
-      const fluctuationPercent =
-        voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT;
-      const voltageMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
-        voltageSampledValueTemplateValue,
-        fluctuationPercent
-      );
-      if (
-        chargingStation.getNumberOfPhases() !== 3 ||
-        (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues())
-      ) {
-        meterValue.sampledValue.push(
-          OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue)
-        );
+    if (!Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
+      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 deferred initialization`
+      )
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []
+    }
+    cp.chargingSchedule.startSchedule = convertToDate(cp.chargingSchedule.startSchedule)
+    cp.validFrom = convertToDate(cp.validFrom)
+    cp.validTo = convertToDate(cp.validTo)
+    let cpReplaced = false
+    if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      for (const [index, chargingProfile] of chargingStation
+        .getConnectorStatus(connectorId)!
+        .chargingProfiles!.entries()) {
+        if (
+          chargingProfile.chargingProfileId === cp.chargingProfileId ||
+          (chargingProfile.stackLevel === cp.stackLevel &&
+            chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
+        ) {
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp
+          cpReplaced = true
+        }
       }
-      for (
-        let phase = 1;
-        chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-        phase++
-      ) {
-        const phaseLineToNeutralValue = `L${phase}-N`;
-        const voltagePhaseLineToNeutralSampledValueTemplate =
-          chargingStation.getSampledValueTemplate(
-            connectorId,
-            OCPP16MeterValueMeasurand.VOLTAGE,
-            phaseLineToNeutralValue as OCPP16MeterValuePhase
-          );
-        let voltagePhaseLineToNeutralMeasurandValue: number;
-        if (voltagePhaseLineToNeutralSampledValueTemplate) {
-          const voltagePhaseLineToNeutralSampledValueTemplateValue =
-            voltagePhaseLineToNeutralSampledValueTemplate.value
-              ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
-              : chargingStation.getVoltageOut();
-          const fluctuationPhaseToNeutralPercent =
-            voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
-            Constants.DEFAULT_FLUCTUATION_PERCENT;
-          voltagePhaseLineToNeutralMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
-            voltagePhaseLineToNeutralSampledValueTemplateValue,
-            fluctuationPhaseToNeutralPercent
-          );
+    }
+    !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
+  }
+
+  public static clearChargingProfiles = (
+    chargingStation: ChargingStation,
+    commandPayload: OCPP16ClearChargingProfileRequest,
+    chargingProfiles: OCPP16ChargingProfile[] | undefined
+  ): boolean => {
+    const { id, chargingProfilePurpose, stackLevel } = commandPayload
+    let clearedCP = false
+    if (isNotEmptyArray(chargingProfiles)) {
+      chargingProfiles.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
+        let clearCurrentCP = false
+        if (chargingProfile.chargingProfileId === id) {
+          clearCurrentCP = true
+        }
+        if (chargingProfilePurpose == null && chargingProfile.stackLevel === stackLevel) {
+          clearCurrentCP = true
+        }
+        if (
+          stackLevel == null &&
+          chargingProfile.chargingProfilePurpose === chargingProfilePurpose
+        ) {
+          clearCurrentCP = true
         }
-        meterValue.sampledValue.push(
-          OCPP16ServiceUtils.buildSampledValue(
-            voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
-            voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
-            null,
-            phaseLineToNeutralValue as OCPP16MeterValuePhase
+        if (
+          chargingProfile.stackLevel === stackLevel &&
+          chargingProfile.chargingProfilePurpose === chargingProfilePurpose
+        ) {
+          clearCurrentCP = true
+        }
+        if (clearCurrentCP) {
+          chargingProfiles.splice(index, 1)
+          logger.debug(
+            `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
+            chargingProfile
           )
-        );
-        if (chargingStation.getPhaseLineToLineVoltageMeterValues()) {
-          const phaseLineToLineValue = `L${phase}-L${
-            (phase + 1) % chargingStation.getNumberOfPhases() !== 0
-              ? (phase + 1) % chargingStation.getNumberOfPhases()
-              : chargingStation.getNumberOfPhases()
-          }`;
-          const voltagePhaseLineToLineSampledValueTemplate =
-            chargingStation.getSampledValueTemplate(
-              connectorId,
-              OCPP16MeterValueMeasurand.VOLTAGE,
-              phaseLineToLineValue as OCPP16MeterValuePhase
-            );
-          let voltagePhaseLineToLineMeasurandValue: number;
-          if (voltagePhaseLineToLineSampledValueTemplate) {
-            const voltagePhaseLineToLineSampledValueTemplateValue =
-              voltagePhaseLineToLineSampledValueTemplate.value
-                ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
-                : Voltage.VOLTAGE_400;
-            const fluctuationPhaseLineToLinePercent =
-              voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
-              Constants.DEFAULT_FLUCTUATION_PERCENT;
-            voltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
-              voltagePhaseLineToLineSampledValueTemplateValue,
-              fluctuationPhaseLineToLinePercent
-            );
-          }
-          const defaultVoltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
-            Voltage.VOLTAGE_400,
-            fluctuationPercent
-          );
-          meterValue.sampledValue.push(
-            OCPP16ServiceUtils.buildSampledValue(
-              voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
-              voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
-              null,
-              phaseLineToLineValue as OCPP16MeterValuePhase
-            )
-          );
+          clearedCP = true
         }
-      }
+      })
     }
-    // Power.Active.Import measurand
-    const powerSampledValueTemplate = chargingStation.getSampledValueTemplate(
-      connectorId,
-      OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
-    );
-    let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
-    if (chargingStation.getNumberOfPhases() === 3) {
-      powerPerPhaseSampledValueTemplates = {
-        L1: chargingStation.getSampledValueTemplate(
-          connectorId,
-          OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L1_N
-        ),
-        L2: chargingStation.getSampledValueTemplate(
-          connectorId,
-          OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L2_N
-        ),
-        L3: chargingStation.getSampledValueTemplate(
-          connectorId,
-          OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L3_N
-        ),
-      };
+    return clearedCP
+  }
+
+  public static composeChargingSchedules = (
+    chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
+    chargingScheduleLower: OCPP16ChargingSchedule | undefined,
+    compositeInterval: Interval
+  ): OCPP16ChargingSchedule | undefined => {
+    if (chargingScheduleHigher == null && chargingScheduleLower == null) {
+      return undefined
     }
-    if (powerSampledValueTemplate) {
-      OCPP16ServiceUtils.checkMeasurandPowerDivider(
-        chargingStation,
-        powerSampledValueTemplate.measurand
-      );
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
-        powerSampledValueTemplate.measurand ??
-        OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
-        chargingStation.templateFile
-      }, cannot calculate ${
-        powerSampledValueTemplate.measurand ??
-        OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      } measurand value`;
-      const powerMeasurandValues = {} as MeasurandValues;
-      const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
-      const maximumPower = Math.round(
-        chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider
-      );
-      const maximumPowerPerPhase = Math.round(
-        chargingStation.getMaximumConfiguredPower() /
-          chargingStation.stationInfo.powerDivider /
-          chargingStation.getNumberOfPhases()
-      );
-      switch (chargingStation.getCurrentOutType()) {
-        case CurrentType.AC:
-          if (chargingStation.getNumberOfPhases() === 3) {
-            const defaultFluctuatedPowerPerPhase =
-              powerSampledValueTemplate.value &&
-              Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerSampledValueTemplate.value) / chargingStation.getNumberOfPhases(),
-                powerSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              );
-            const phase1FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L1?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerPerPhaseSampledValueTemplates.L1.value),
-                powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              );
-            const phase2FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L2?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerPerPhaseSampledValueTemplates.L2.value),
-                powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              );
-            const phase3FluctuatedValue =
-              powerPerPhaseSampledValueTemplates?.L3?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerPerPhaseSampledValueTemplates.L3.value),
-                powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              );
-            powerMeasurandValues.L1 =
-              phase1FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
-              Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider);
-            powerMeasurandValues.L2 =
-              phase2FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
-              Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider);
-            powerMeasurandValues.L3 =
-              phase3FluctuatedValue ??
-              defaultFluctuatedPowerPerPhase ??
-              Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider);
-          } else {
-            powerMeasurandValues.L1 = powerSampledValueTemplate.value
-              ? Utils.getRandomFloatFluctuatedRounded(
-                  parseInt(powerSampledValueTemplate.value),
-                  powerSampledValueTemplate.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-              : Utils.getRandomFloatRounded(maximumPower / unitDivider);
-            powerMeasurandValues.L2 = 0;
-            powerMeasurandValues.L3 = 0;
-          }
-          powerMeasurandValues.allPhases = Utils.roundTo(
-            powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
-            2
-          );
-          break;
-        case CurrentType.DC:
-          powerMeasurandValues.allPhases = powerSampledValueTemplate.value
-            ? Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerSampledValueTemplate.value),
-                powerSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              )
-            : Utils.getRandomFloatRounded(maximumPower / unitDivider);
-          break;
-        default:
-          logger.error(errMsg);
-          throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-      }
-      meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(
-          powerSampledValueTemplate,
-          powerMeasurandValues.allPhases
-        )
-      );
-      const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      const maximumPowerRounded = Utils.roundTo(maximumPower / unitDivider, 2);
-      if (
-        Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
-          maximumPowerRounded ||
-        debug
-      ) {
-        logger.error(
-          `${chargingStation.logPrefix()} MeterValues measurand ${
-            meterValue.sampledValue[sampledValuesIndex].measurand ??
-            OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
-            meterValue.sampledValue[sampledValuesIndex].value
-          }/${maximumPowerRounded}`
-        );
-      }
-      for (
-        let phase = 1;
-        chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-        phase++
-      ) {
-        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
+    if (chargingScheduleHigher != null && chargingScheduleLower == null) {
+      return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval)
+    }
+    if (chargingScheduleHigher == null && chargingScheduleLower != null) {
+      return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval)
+    }
+    const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined =
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval)
+    const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined =
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval)
+    const compositeChargingScheduleHigherInterval: Interval = {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      start: compositeChargingScheduleHigher!.startSchedule!,
+      end: addSeconds(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        compositeChargingScheduleHigher!.startSchedule!,
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        compositeChargingScheduleHigher!.duration!
+      )
+    }
+    const compositeChargingScheduleLowerInterval: Interval = {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      start: compositeChargingScheduleLower!.startSchedule!,
+      end: addSeconds(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        compositeChargingScheduleLower!.startSchedule!,
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        compositeChargingScheduleLower!.duration!
+      )
+    }
+    const higherFirst = isBefore(
+      compositeChargingScheduleHigherInterval.start,
+      compositeChargingScheduleLowerInterval.start
+    )
+    if (
+      !areIntervalsOverlapping(
+        compositeChargingScheduleHigherInterval,
+        compositeChargingScheduleLowerInterval
+      )
+    ) {
+      return {
+        ...compositeChargingScheduleLower,
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        ...compositeChargingScheduleHigher!,
+        startSchedule: higherFirst
+          ? (compositeChargingScheduleHigherInterval.start as Date)
+          : (compositeChargingScheduleLowerInterval.start as Date),
+        duration: higherFirst
+          ? differenceInSeconds(
+            compositeChargingScheduleLowerInterval.end,
+            compositeChargingScheduleHigherInterval.start
           )
-        );
-        const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
-        const maximumPowerPerPhaseRounded = Utils.roundTo(maximumPowerPerPhase / unitDivider, 2);
-        if (
-          Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
-            maximumPowerPerPhaseRounded ||
-          debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
-              OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-            }: phase ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-            }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].value
-            }/${maximumPowerPerPhaseRounded}`
-          );
-        }
+          : differenceInSeconds(
+            compositeChargingScheduleHigherInterval.end,
+            compositeChargingScheduleLowerInterval.start
+          ),
+        chargingSchedulePeriod: [
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map(schedulePeriod => {
+            return {
+              ...schedulePeriod,
+              startPeriod: higherFirst
+                ? 0
+                : schedulePeriod.startPeriod +
+                  differenceInSeconds(
+                    compositeChargingScheduleHigherInterval.start,
+                    compositeChargingScheduleLowerInterval.start
+                  )
+            }
+          }),
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          ...compositeChargingScheduleLower!.chargingSchedulePeriod.map(schedulePeriod => {
+            return {
+              ...schedulePeriod,
+              startPeriod: higherFirst
+                ? schedulePeriod.startPeriod +
+                  differenceInSeconds(
+                    compositeChargingScheduleLowerInterval.start,
+                    compositeChargingScheduleHigherInterval.start
+                  )
+                : 0
+            }
+          })
+        ].sort((a, b) => a.startPeriod - b.startPeriod)
       }
     }
-    // Current.Import measurand
-    const currentSampledValueTemplate = chargingStation.getSampledValueTemplate(
-      connectorId,
-      OCPP16MeterValueMeasurand.CURRENT_IMPORT
-    );
-    let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
-    if (chargingStation.getNumberOfPhases() === 3) {
-      currentPerPhaseSampledValueTemplates = {
-        L1: chargingStation.getSampledValueTemplate(
-          connectorId,
-          OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L1
-        ),
-        L2: chargingStation.getSampledValueTemplate(
-          connectorId,
-          OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L2
-        ),
-        L3: chargingStation.getSampledValueTemplate(
-          connectorId,
-          OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L3
+    return {
+      ...compositeChargingScheduleLower,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      ...compositeChargingScheduleHigher!,
+      startSchedule: higherFirst
+        ? (compositeChargingScheduleHigherInterval.start as Date)
+        : (compositeChargingScheduleLowerInterval.start as Date),
+      duration: higherFirst
+        ? differenceInSeconds(
+          compositeChargingScheduleLowerInterval.end,
+          compositeChargingScheduleHigherInterval.start
+        )
+        : differenceInSeconds(
+          compositeChargingScheduleHigherInterval.end,
+          compositeChargingScheduleLowerInterval.start
         ),
-      };
-    }
-    if (currentSampledValueTemplate) {
-      OCPP16ServiceUtils.checkMeasurandPowerDivider(
-        chargingStation,
-        currentSampledValueTemplate.measurand
-      );
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
-        currentSampledValueTemplate.measurand ??
-        OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
-        chargingStation.templateFile
-      }, cannot calculate ${
-        currentSampledValueTemplate.measurand ??
-        OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      } measurand value`;
-      const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
-      let maximumAmperage: number;
-      switch (chargingStation.getCurrentOutType()) {
-        case CurrentType.AC:
-          maximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
-            chargingStation.getNumberOfPhases(),
-            chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider,
-            chargingStation.getVoltageOut()
-          );
-          if (chargingStation.getNumberOfPhases() === 3) {
-            const defaultFluctuatedAmperagePerPhase =
-              currentSampledValueTemplate.value &&
-              Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentSampledValueTemplate.value),
-                currentSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              );
-            const phase1FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L1?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentPerPhaseSampledValueTemplates.L1.value),
-                currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              );
-            const phase2FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L2?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentPerPhaseSampledValueTemplates.L2.value),
-                currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              );
-            const phase3FluctuatedValue =
-              currentPerPhaseSampledValueTemplates?.L3?.value &&
-              Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentPerPhaseSampledValueTemplates.L3.value),
-                currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
-              );
-            currentMeasurandValues.L1 =
-              phase1FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
-              Utils.getRandomFloatRounded(maximumAmperage);
-            currentMeasurandValues.L2 =
-              phase2FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
-              Utils.getRandomFloatRounded(maximumAmperage);
-            currentMeasurandValues.L3 =
-              phase3FluctuatedValue ??
-              defaultFluctuatedAmperagePerPhase ??
-              Utils.getRandomFloatRounded(maximumAmperage);
-          } else {
-            currentMeasurandValues.L1 = currentSampledValueTemplate.value
-              ? Utils.getRandomFloatFluctuatedRounded(
-                  parseInt(currentSampledValueTemplate.value),
-                  currentSampledValueTemplate.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT
+      chargingSchedulePeriod: [
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map(schedulePeriod => {
+          return {
+            ...schedulePeriod,
+            startPeriod: higherFirst
+              ? 0
+              : schedulePeriod.startPeriod +
+                differenceInSeconds(
+                  compositeChargingScheduleHigherInterval.start,
+                  compositeChargingScheduleLowerInterval.start
                 )
-              : Utils.getRandomFloatRounded(maximumAmperage);
-            currentMeasurandValues.L2 = 0;
-            currentMeasurandValues.L3 = 0;
           }
-          currentMeasurandValues.allPhases = Utils.roundTo(
-            (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
-              chargingStation.getNumberOfPhases(),
-            2
-          );
-          break;
-        case CurrentType.DC:
-          maximumAmperage = DCElectricUtils.amperage(
-            chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider,
-            chargingStation.getVoltageOut()
-          );
-          currentMeasurandValues.allPhases = currentSampledValueTemplate.value
-            ? Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentSampledValueTemplate.value),
-                currentSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT
+        }),
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        ...compositeChargingScheduleLower!.chargingSchedulePeriod
+          .filter((schedulePeriod, index) => {
+            if (
+              higherFirst &&
+              isWithinInterval(
+                addSeconds(
+                  compositeChargingScheduleLowerInterval.start,
+                  schedulePeriod.startPeriod
+                ),
+                {
+                  start: compositeChargingScheduleLowerInterval.start,
+                  end: compositeChargingScheduleHigherInterval.end
+                }
               )
-            : Utils.getRandomFloatRounded(maximumAmperage);
-          break;
-        default:
-          logger.error(errMsg);
-          throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-      }
-      meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(
-          currentSampledValueTemplate,
-          currentMeasurandValues.allPhases
-        )
-      );
-      const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      if (
-        Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maximumAmperage ||
-        debug
-      ) {
-        logger.error(
-          `${chargingStation.logPrefix()} MeterValues measurand ${
-            meterValue.sampledValue[sampledValuesIndex].measurand ??
-            OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
-            meterValue.sampledValue[sampledValuesIndex].value
-          }/${maximumAmperage}`
-        );
-      }
-      for (
-        let phase = 1;
-        chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-        phase++
-      ) {
-        const phaseValue = `L${phase}`;
-        meterValue.sampledValue.push(
-          OCPP16ServiceUtils.buildSampledValue(
-            (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
-              currentSampledValueTemplate,
-            currentMeasurandValues[phaseValue] as number,
-            null,
-            phaseValue as OCPP16MeterValuePhase
-          )
-        );
-        const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
-        if (
-          Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
-            maximumAmperage ||
-          debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
-              OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-            }: phase ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-            }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].value
-            }/${maximumAmperage}`
-          );
-        }
-      }
+            ) {
+              return false
+            }
+            if (
+              higherFirst &&
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 &&
+              !isWithinInterval(
+                addSeconds(
+                  compositeChargingScheduleLowerInterval.start,
+                  schedulePeriod.startPeriod
+                ),
+                {
+                  start: compositeChargingScheduleLowerInterval.start,
+                  end: compositeChargingScheduleHigherInterval.end
+                }
+              ) &&
+              isWithinInterval(
+                addSeconds(
+                  compositeChargingScheduleLowerInterval.start,
+                  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+                  compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod
+                ),
+                {
+                  start: compositeChargingScheduleLowerInterval.start,
+                  end: compositeChargingScheduleHigherInterval.end
+                }
+              )
+            ) {
+              return false
+            }
+            if (
+              !higherFirst &&
+              isWithinInterval(
+                addSeconds(
+                  compositeChargingScheduleLowerInterval.start,
+                  schedulePeriod.startPeriod
+                ),
+                {
+                  start: compositeChargingScheduleHigherInterval.start,
+                  end: compositeChargingScheduleLowerInterval.end
+                }
+              )
+            ) {
+              return false
+            }
+            return true
+          })
+          .map((schedulePeriod, index) => {
+            if (index === 0 && schedulePeriod.startPeriod !== 0) {
+              schedulePeriod.startPeriod = 0
+            }
+            return {
+              ...schedulePeriod,
+              startPeriod: higherFirst
+                ? schedulePeriod.startPeriod +
+                  differenceInSeconds(
+                    compositeChargingScheduleLowerInterval.start,
+                    compositeChargingScheduleHigherInterval.start
+                  )
+                : 0
+            }
+          })
+      ].sort((a, b) => a.startPeriod - b.startPeriod)
     }
-    // Energy.Active.Import.Register measurand (default)
-    const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
-    if (energySampledValueTemplate) {
-      OCPP16ServiceUtils.checkMeasurandPowerDivider(
-        chargingStation,
-        energySampledValueTemplate.measurand
-      );
-      const unitDivider =
-        energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
-      const maximumEnergyRounded = Utils.roundTo(
-        ((chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider) *
-          interval) /
-          (3600 * 1000),
-        2
-      );
-      const energyValueRounded = energySampledValueTemplate.value
-        ? // Cumulate the fluctuated value around the static one
-          Utils.getRandomFloatFluctuatedRounded(
-            parseInt(energySampledValueTemplate.value),
-            energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
-          )
-        : Utils.getRandomFloatRounded(maximumEnergyRounded);
-      // Persist previous value on connector
-      if (
-        connector &&
-        !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) &&
-        connector.energyActiveImportRegisterValue >= 0 &&
-        !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) &&
-        connector.transactionEnergyActiveImportRegisterValue >= 0
-      ) {
-        connector.energyActiveImportRegisterValue += energyValueRounded;
-        connector.transactionEnergyActiveImportRegisterValue += energyValueRounded;
-      } else {
-        connector.energyActiveImportRegisterValue = 0;
-        connector.transactionEnergyActiveImportRegisterValue = 0;
-      }
-      meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(
-          energySampledValueTemplate,
-          Utils.roundTo(
-            chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
-              unitDivider,
-            2
-          )
-        )
-      );
-      const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      if (energyValueRounded > maximumEnergyRounded || debug) {
-        logger.error(
-          `${chargingStation.logPrefix()} MeterValues measurand ${
-            meterValue.sampledValue[sampledValuesIndex].measurand ??
-            OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connectorId ${connectorId}, transaction ${
-            connector.transactionId
-          }, value: ${energyValueRounded}/${maximumEnergyRounded}, duration: ${Utils.roundTo(
-            interval / (3600 * 1000),
-            4
-          )}h`
-        );
-      }
+  }
+
+  public static isConfigurationKeyVisible (key: ConfigurationKey): boolean {
+    if (key.visible == null) {
+      return true
     }
-    return meterValue;
+    return key.visible
   }
 
-  public static buildTransactionBeginMeterValue(
+  public static hasReservation = (
     chargingStation: ChargingStation,
     connectorId: number,
-    meterStart: number
-  ): OCPP16MeterValue {
-    const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
-      sampledValue: [],
-    };
-    // Energy.Active.Import.Register measurand (default)
-    const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
-    const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
-    meterValue.sampledValue.push(
-      OCPP16ServiceUtils.buildSampledValue(
-        sampledValueTemplate,
-        Utils.roundTo(meterStart / unitDivider, 4),
-        MeterValueContext.TRANSACTION_BEGIN
+    idTag: string
+  ): boolean => {
+    const connectorReservation = chargingStation.getReservationBy('connectorId', connectorId)
+    const chargingStationReservation = chargingStation.getReservationBy('connectorId', 0)
+    if (
+      (chargingStation.getConnectorStatus(connectorId)?.status ===
+        OCPP16ChargePointStatus.Reserved &&
+        connectorReservation != null &&
+        !hasReservationExpired(connectorReservation) &&
+        connectorReservation.idTag === idTag) ||
+      (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved &&
+        chargingStationReservation != null &&
+        !hasReservationExpired(chargingStationReservation) &&
+        chargingStationReservation.idTag === idTag)
+    ) {
+      logger.debug(
+        `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
+        connectorReservation ?? chargingStationReservation
       )
-    );
-    return meterValue;
+      return true
+    }
+    return false
   }
 
-  public static buildTransactionEndMeterValue(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    meterStop: number
-  ): OCPP16MeterValue {
-    const meterValue: OCPP16MeterValue = {
-      timestamp: new Date().toISOString(),
-      sampledValue: [],
-    };
-    // Energy.Active.Import.Register measurand (default)
-    const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
-    const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
-    meterValue.sampledValue.push(
-      OCPP16ServiceUtils.buildSampledValue(
-        sampledValueTemplate,
-        Utils.roundTo(meterStop / unitDivider, 4),
-        MeterValueContext.TRANSACTION_END
-      )
-    );
-    return meterValue;
+  public static parseJsonSchemaFile<T extends JsonType>(
+    relativePath: string,
+    moduleName?: string,
+    methodName?: string
+  ): JSONSchemaType<T> {
+    return super.parseJsonSchemaFile<T>(
+      relativePath,
+      OCPPVersion.VERSION_16,
+      moduleName,
+      methodName
+    )
   }
 
-  public static buildTransactionDataMeterValues(
-    transactionBeginMeterValue: OCPP16MeterValue,
-    transactionEndMeterValue: OCPP16MeterValue
-  ): OCPP16MeterValue[] {
-    const meterValues: OCPP16MeterValue[] = [];
-    meterValues.push(transactionBeginMeterValue);
-    meterValues.push(transactionEndMeterValue);
-    return meterValues;
+  private static readonly composeChargingSchedule = (
+    chargingSchedule: OCPP16ChargingSchedule,
+    compositeInterval: Interval
+  ): OCPP16ChargingSchedule | undefined => {
+    const chargingScheduleInterval: Interval = {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      start: chargingSchedule.startSchedule!,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!)
+    }
+    if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
+      chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod)
+      if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
+        return {
+          ...chargingSchedule,
+          startSchedule: compositeInterval.start as Date,
+          duration: differenceInSeconds(
+            chargingScheduleInterval.end,
+            compositeInterval.start as Date
+          ),
+          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod
+            .filter((schedulePeriod, index) => {
+              if (
+                isWithinInterval(
+                  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+                  addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
+                  compositeInterval
+                )
+              ) {
+                return true
+              }
+              if (
+                index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
+                !isWithinInterval(
+                  addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
+                  compositeInterval
+                ) &&
+                isWithinInterval(
+                  addSeconds(
+                    chargingScheduleInterval.start,
+                    chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod
+                  ),
+                  compositeInterval
+                )
+              ) {
+                return true
+              }
+              return false
+            })
+            .map((schedulePeriod, index) => {
+              if (index === 0 && schedulePeriod.startPeriod !== 0) {
+                schedulePeriod.startPeriod = 0
+              }
+              return schedulePeriod
+            })
+        }
+      }
+      if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
+        return {
+          ...chargingSchedule,
+          duration: differenceInSeconds(
+            compositeInterval.end as Date,
+            chargingScheduleInterval.start
+          ),
+          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter(schedulePeriod =>
+            isWithinInterval(
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
+              compositeInterval
+            )
+          )
+        }
+      }
+      return chargingSchedule
+    }
   }
 }