fix: ensure custom meterValues values are taken
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
index 6765722c9b28b0276f592b03aacf02236759c44c..a752ac9927e2932d4eac5700acb7505db20c9903 100644 (file)
@@ -1,6 +1,14 @@
 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
 import type { JSONSchemaType } from 'ajv';
+import {
+  addSeconds,
+  areIntervalsOverlapping,
+  differenceInSeconds,
+  isAfter,
+  isBefore,
+  isWithinInterval,
+} from 'date-fns';
 
 import { OCPP16Constants } from './OCPP16Constants';
 import {
@@ -25,6 +33,7 @@ import {
   type OCPP16ChangeAvailabilityResponse,
   OCPP16ChargePointStatus,
   type OCPP16ChargingProfile,
+  type OCPP16ChargingSchedule,
   type OCPP16IncomingRequestCommand,
   type OCPP16MeterValue,
   OCPP16MeterValueMeasurand,
@@ -36,7 +45,6 @@ import {
   type OCPP16SupportedFeatureProfiles,
   OCPPVersion,
   type SampledValueTemplate,
-  Voltage,
 } from '../../../types';
 import {
   ACElectricUtils,
@@ -48,6 +56,7 @@ import {
   getRandomFloatRounded,
   getRandomInteger,
   isNotEmptyArray,
+  isNotEmptyString,
   isNullOrUndefined,
   isUndefined,
   logger,
@@ -93,7 +102,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     if (socSampledValueTemplate) {
       const socMaximumValue = 100;
       const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0;
-      const socSampledValueTemplateValue = socSampledValueTemplate.value
+      const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value)
         ? getRandomFloatFluctuatedRounded(
             parseInt(socSampledValueTemplate.value),
             socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -114,7 +123,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
           }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
             meterValue.sampledValue[sampledValuesIndex].value
-          }/${socMaximumValue}}`,
+          }/${socMaximumValue}`,
         );
       }
     }
@@ -125,9 +134,9 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       OCPP16MeterValueMeasurand.VOLTAGE,
     );
     if (voltageSampledValueTemplate) {
-      const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value
+      const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value)
         ? parseInt(voltageSampledValueTemplate.value)
-        : chargingStation.getVoltageOut();
+        : chargingStation.stationInfo.voltageOut!;
       const fluctuationPercent =
         voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT;
       const voltageMeasurandValue = getRandomFloatFluctuatedRounded(
@@ -136,7 +145,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       );
       if (
         chargingStation.getNumberOfPhases() !== 3 ||
-        (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues())
+        (chargingStation.getNumberOfPhases() === 3 &&
+          chargingStation.stationInfo?.mainVoltageMeterValues)
       ) {
         meterValue.sampledValue.push(
           OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue),
@@ -157,10 +167,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           );
         let voltagePhaseLineToNeutralMeasurandValue: number | undefined;
         if (voltagePhaseLineToNeutralSampledValueTemplate) {
-          const voltagePhaseLineToNeutralSampledValueTemplateValue =
-            voltagePhaseLineToNeutralSampledValueTemplate.value
-              ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
-              : chargingStation.getVoltageOut();
+          const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString(
+            voltagePhaseLineToNeutralSampledValueTemplate.value,
+          )
+            ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
+            : chargingStation.stationInfo.voltageOut!;
           const fluctuationPhaseToNeutralPercent =
             voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
             Constants.DEFAULT_FLUCTUATION_PERCENT;
@@ -177,7 +188,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             phaseLineToNeutralValue as OCPP16MeterValuePhase,
           ),
         );
-        if (chargingStation.getPhaseLineToLineVoltageMeterValues()) {
+        if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) {
           const phaseLineToLineValue = `L${phase}-L${
             (phase + 1) % chargingStation.getNumberOfPhases() !== 0
               ? (phase + 1) % chargingStation.getNumberOfPhases()
@@ -192,10 +203,15 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             );
           let voltagePhaseLineToLineMeasurandValue: number | undefined;
           if (voltagePhaseLineToLineSampledValueTemplate) {
-            const voltagePhaseLineToLineSampledValueTemplateValue =
-              voltagePhaseLineToLineSampledValueTemplate.value
-                ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
-                : Voltage.VOLTAGE_400;
+            const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString(
+              voltagePhaseLineToLineSampledValueTemplate.value,
+            )
+              ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
+              : roundTo(
+                  Math.sqrt(chargingStation.getNumberOfPhases()) *
+                    chargingStation.stationInfo.voltageOut!,
+                  2,
+                );
             const fluctuationPhaseLineToLinePercent =
               voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
               Constants.DEFAULT_FLUCTUATION_PERCENT;
@@ -205,7 +221,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             );
           }
           const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
-            Voltage.VOLTAGE_400,
+            Math.sqrt(chargingStation.getNumberOfPhases()) *
+              chargingStation.stationInfo.voltageOut!,
             fluctuationPercent,
           );
           meterValue.sampledValue.push(
@@ -256,7 +273,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       const errMsg = `MeterValues measurand ${
         powerSampledValueTemplate.measurand ??
         OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
+      }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
         chargingStation.templateFile
       }, cannot calculate ${
         powerSampledValueTemplate.measurand ??
@@ -274,7 +291,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       const connectorMinimumPowerPerPhase = Math.round(
         connectorMinimumPower / chargingStation.getNumberOfPhases(),
       );
-      switch (chargingStation.getCurrentOutType()) {
+      switch (chargingStation.stationInfo?.currentOutType) {
         case CurrentType.AC:
           if (chargingStation.getNumberOfPhases() === 3) {
             const defaultFluctuatedPowerPerPhase =
@@ -283,7 +300,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerSampledValueTemplate.value,
                   connectorMaximumPower / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ) / chargingStation.getNumberOfPhases(),
                 powerSampledValueTemplate.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -294,7 +314,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L1.value,
                   connectorMaximumPowerPerPhase / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -305,7 +328,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L2.value,
                   connectorMaximumPowerPerPhase / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -316,7 +342,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerPerPhaseSampledValueTemplates.L3.value,
                   connectorMaximumPowerPerPhase / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -343,12 +372,15 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 connectorMinimumPowerPerPhase / unitDivider,
               );
           } else {
-            powerMeasurandValues.L1 = powerSampledValueTemplate.value
+            powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value)
               ? getRandomFloatFluctuatedRounded(
                   OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                     powerSampledValueTemplate.value,
                     connectorMaximumPower / unitDivider,
-                    { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                    },
                   ),
                   powerSampledValueTemplate.fluctuationPercent ??
                     Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -366,12 +398,15 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           );
           break;
         case CurrentType.DC:
-          powerMeasurandValues.allPhases = powerSampledValueTemplate.value
+          powerMeasurandValues.allPhases = isNotEmptyString(powerSampledValueTemplate.value)
             ? getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerSampledValueTemplate.value,
                   connectorMaximumPower / unitDivider,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 powerSampledValueTemplate.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -492,7 +527,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       const errMsg = `MeterValues measurand ${
         currentSampledValueTemplate.measurand ??
         OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
+      }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
         chargingStation.templateFile
       }, cannot calculate ${
         currentSampledValueTemplate.measurand ??
@@ -503,12 +538,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         chargingStation.getConnectorMaximumAvailablePower(connectorId);
       const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
       let connectorMaximumAmperage: number;
-      switch (chargingStation.getCurrentOutType()) {
+      switch (chargingStation.stationInfo?.currentOutType) {
         case CurrentType.AC:
           connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
             chargingStation.getNumberOfPhases(),
             connectorMaximumAvailablePower,
-            chargingStation.getVoltageOut(),
+            chargingStation.stationInfo.voltageOut!,
           );
           if (chargingStation.getNumberOfPhases() === 3) {
             const defaultFluctuatedAmperagePerPhase =
@@ -517,7 +552,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentSampledValueTemplate.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 currentSampledValueTemplate.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -528,7 +566,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L1.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -539,7 +580,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L2.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -550,7 +594,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentPerPhaseSampledValueTemplates.L3.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -568,12 +615,15 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               (defaultFluctuatedAmperagePerPhase as number) ??
               getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
           } else {
-            currentMeasurandValues.L1 = currentSampledValueTemplate.value
+            currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value)
               ? getRandomFloatFluctuatedRounded(
                   OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                     currentSampledValueTemplate.value,
                     connectorMaximumAmperage,
-                    { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                    },
                   ),
                   currentSampledValueTemplate.fluctuationPercent ??
                     Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -591,14 +641,17 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         case CurrentType.DC:
           connectorMaximumAmperage = DCElectricUtils.amperage(
             connectorMaximumAvailablePower,
-            chargingStation.getVoltageOut(),
+            chargingStation.stationInfo.voltageOut!,
           );
-          currentMeasurandValues.allPhases = currentSampledValueTemplate.value
+          currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value)
             ? getRandomFloatFluctuatedRounded(
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentSampledValueTemplate.value,
                   connectorMaximumAmperage,
-                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
+                  {
+                    limitationEnabled:
+                      chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  },
                 ),
                 currentSampledValueTemplate.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT,
@@ -687,14 +740,14 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         (connectorMaximumAvailablePower * interval) / (3600 * 1000),
         2,
       );
-      const energyValueRounded = energySampledValueTemplate.value
+      const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value)
         ? // Cumulate the fluctuated value around the static one
           getRandomFloatFluctuatedRounded(
             OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
               energySampledValueTemplate.value,
               connectorMaximumEnergyRounded,
               {
-                limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(),
+                limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues,
                 unitMultiplier: unitDivider,
               },
             ),
@@ -863,7 +916,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
     ) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`,
+        `${chargingStation.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`,
       );
       chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
     }
@@ -890,28 +943,23 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     commandPayload: ClearChargingProfileRequest,
     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 === commandPayload.id) {
+        if (chargingProfile.chargingProfileId === id) {
           clearCurrentCP = true;
         }
-        if (
-          !commandPayload.chargingProfilePurpose &&
-          chargingProfile.stackLevel === commandPayload.stackLevel
-        ) {
+        if (!chargingProfilePurpose && chargingProfile.stackLevel === stackLevel) {
           clearCurrentCP = true;
         }
-        if (
-          !chargingProfile.stackLevel &&
-          chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
-        ) {
+        if (!stackLevel && chargingProfile.chargingProfilePurpose === chargingProfilePurpose) {
           clearCurrentCP = true;
         }
         if (
-          chargingProfile.stackLevel === commandPayload.stackLevel &&
-          chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
+          chargingProfile.stackLevel === stackLevel &&
+          chargingProfile.chargingProfilePurpose === chargingProfilePurpose
         ) {
           clearCurrentCP = true;
         }
@@ -928,6 +976,198 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return clearedCP;
   };
 
+  public static composeChargingSchedules = (
+    chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
+    chargingScheduleLower: OCPP16ChargingSchedule | undefined,
+    compositeInterval: Interval,
+  ): OCPP16ChargingSchedule | undefined => {
+    if (!chargingScheduleHigher && !chargingScheduleLower) {
+      return undefined;
+    }
+    if (chargingScheduleHigher && !chargingScheduleLower) {
+      return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval);
+    }
+    if (!chargingScheduleHigher && chargingScheduleLower) {
+      return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval);
+    }
+    const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined =
+      OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval);
+    const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined =
+      OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval);
+    const compositeChargingScheduleHigherInterval: Interval = {
+      start: compositeChargingScheduleHigher!.startSchedule!,
+      end: addSeconds(
+        compositeChargingScheduleHigher!.startSchedule!,
+        compositeChargingScheduleHigher!.duration!,
+      ),
+    };
+    const compositeChargingScheduleLowerInterval: Interval = {
+      start: compositeChargingScheduleLower!.startSchedule!,
+      end: addSeconds(
+        compositeChargingScheduleLower!.startSchedule!,
+        compositeChargingScheduleLower!.duration!,
+      ),
+    };
+    const higherFirst = isBefore(
+      compositeChargingScheduleHigherInterval.start,
+      compositeChargingScheduleLowerInterval.start,
+    );
+    if (
+      !areIntervalsOverlapping(
+        compositeChargingScheduleHigherInterval,
+        compositeChargingScheduleLowerInterval,
+      )
+    ) {
+      return {
+        ...compositeChargingScheduleLower,
+        ...compositeChargingScheduleHigher!,
+        startSchedule: higherFirst
+          ? (compositeChargingScheduleHigherInterval.start as Date)
+          : (compositeChargingScheduleLowerInterval.start as Date),
+        duration: higherFirst
+          ? differenceInSeconds(
+              compositeChargingScheduleLowerInterval.end,
+              compositeChargingScheduleHigherInterval.start,
+            )
+          : differenceInSeconds(
+              compositeChargingScheduleHigherInterval.end,
+              compositeChargingScheduleLowerInterval.start,
+            ),
+        chargingSchedulePeriod: [
+          ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => {
+            return {
+              ...schedulePeriod,
+              startPeriod: higherFirst
+                ? 0
+                : schedulePeriod.startPeriod +
+                  differenceInSeconds(
+                    compositeChargingScheduleHigherInterval.start,
+                    compositeChargingScheduleLowerInterval.start,
+                  ),
+            };
+          }),
+          ...compositeChargingScheduleLower!.chargingSchedulePeriod.map((schedulePeriod) => {
+            return {
+              ...schedulePeriod,
+              startPeriod: higherFirst
+                ? schedulePeriod.startPeriod +
+                  differenceInSeconds(
+                    compositeChargingScheduleLowerInterval.start,
+                    compositeChargingScheduleHigherInterval.start,
+                  )
+                : 0,
+            };
+          }),
+        ].sort((a, b) => a.startPeriod - b.startPeriod),
+      };
+    }
+    return {
+      ...compositeChargingScheduleLower,
+      ...compositeChargingScheduleHigher!,
+      startSchedule: higherFirst
+        ? (compositeChargingScheduleHigherInterval.start as Date)
+        : (compositeChargingScheduleLowerInterval.start as Date),
+      duration: higherFirst
+        ? differenceInSeconds(
+            compositeChargingScheduleLowerInterval.end,
+            compositeChargingScheduleHigherInterval.start,
+          )
+        : differenceInSeconds(
+            compositeChargingScheduleHigherInterval.end,
+            compositeChargingScheduleLowerInterval.start,
+          ),
+      chargingSchedulePeriod: [
+        ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => {
+          return {
+            ...schedulePeriod,
+            startPeriod: higherFirst
+              ? 0
+              : schedulePeriod.startPeriod +
+                differenceInSeconds(
+                  compositeChargingScheduleHigherInterval.start,
+                  compositeChargingScheduleLowerInterval.start,
+                ),
+          };
+        }),
+        ...compositeChargingScheduleLower!.chargingSchedulePeriod
+          .filter((schedulePeriod, index) => {
+            if (
+              higherFirst &&
+              isWithinInterval(
+                addSeconds(
+                  compositeChargingScheduleLowerInterval.start,
+                  schedulePeriod.startPeriod,
+                ),
+                {
+                  start: compositeChargingScheduleLowerInterval.start,
+                  end: compositeChargingScheduleHigherInterval.end,
+                },
+              )
+            ) {
+              return false;
+            }
+            if (
+              higherFirst &&
+              index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 &&
+              !isWithinInterval(
+                addSeconds(
+                  compositeChargingScheduleLowerInterval.start,
+                  schedulePeriod.startPeriod,
+                ),
+                {
+                  start: compositeChargingScheduleLowerInterval.start,
+                  end: compositeChargingScheduleHigherInterval.end,
+                },
+              ) &&
+              isWithinInterval(
+                addSeconds(
+                  compositeChargingScheduleLowerInterval.start,
+                  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),
+    };
+  };
+
   public static hasReservation = (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -947,6 +1187,10 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         !hasReservationExpired(chargingStationReservation) &&
         chargingStationReservation?.idTag === idTag)
     ) {
+      logger.debug(
+        `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
+        connectorReservation ?? chargingStationReservation,
+      );
       return true;
     }
     return false;
@@ -965,18 +1209,91 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     );
   }
 
+  private static composeChargingSchedule = (
+    chargingSchedule: OCPP16ChargingSchedule,
+    compositeInterval: Interval,
+  ): OCPP16ChargingSchedule | undefined => {
+    const chargingScheduleInterval: Interval = {
+      start: chargingSchedule.startSchedule!,
+      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(
+                  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(
+              addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
+              compositeInterval,
+            ),
+          ),
+        };
+      }
+      return chargingSchedule;
+    }
+  };
+
   private static buildSampledValue(
     sampledValueTemplate: SampledValueTemplate,
     value: number,
     context?: MeterValueContext,
     phase?: OCPP16MeterValuePhase,
   ): OCPP16SampledValue {
-    const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
-    const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
+    const sampledValueValue = value ?? sampledValueTemplate?.value;
+    const sampledValueContext = context ?? sampledValueTemplate?.context;
     const sampledValueLocation =
       sampledValueTemplate?.location ??
       OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
-    const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
+    const sampledValuePhase = phase ?? sampledValueTemplate?.phase;
     return {
       ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
         unit: sampledValueTemplate.unit,