Merge pull request #813 from SAP/combined-prs-branch
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
index b510051e2085a3ce0d006a1765f05be7603a3318..17da98c08a6333c14a5fb83eba3725ad5321775a 100644 (file)
@@ -933,49 +933,195 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   };
 
   public static composeChargingSchedules = (
-    chargingSchedule1: OCPP16ChargingSchedule | undefined,
-    chargingSchedule2: OCPP16ChargingSchedule | undefined,
-    targetInterval: Interval,
+    chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
+    chargingScheduleLower: OCPP16ChargingSchedule | undefined,
+    compositeInterval: Interval,
   ): OCPP16ChargingSchedule | undefined => {
-    if (!chargingSchedule1 && !chargingSchedule2) {
+    if (!chargingScheduleHigher && !chargingScheduleLower) {
       return undefined;
     }
-    if (chargingSchedule1 && !chargingSchedule2) {
-      return OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1, targetInterval);
+    if (chargingScheduleHigher && !chargingScheduleLower) {
+      return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval);
     }
-    if (!chargingSchedule1 && chargingSchedule2) {
-      return OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2, targetInterval);
+    if (!chargingScheduleHigher && chargingScheduleLower) {
+      return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval);
     }
-    const compositeChargingSchedule1: OCPP16ChargingSchedule | undefined =
-      OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1!, targetInterval);
-    const compositeChargingSchedule2: OCPP16ChargingSchedule | undefined =
-      OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2!, targetInterval);
-    const compositeChargingScheduleInterval1: Interval = {
-      start: compositeChargingSchedule1!.startSchedule!,
+    const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined =
+      OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval);
+    const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined =
+      OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval);
+    const compositeChargingScheduleHigherInterval: Interval = {
+      start: compositeChargingScheduleHigher!.startSchedule!,
       end: addSeconds(
-        compositeChargingSchedule1!.startSchedule!,
-        compositeChargingSchedule1!.duration!,
+        compositeChargingScheduleHigher!.startSchedule!,
+        compositeChargingScheduleHigher!.duration!,
       ),
     };
-    const compositeChargingScheduleInterval2: Interval = {
-      start: compositeChargingSchedule2!.startSchedule!,
+    const compositeChargingScheduleLowerInterval: Interval = {
+      start: compositeChargingScheduleLower!.startSchedule!,
       end: addSeconds(
-        compositeChargingSchedule2!.startSchedule!,
-        compositeChargingSchedule2!.duration!,
+        compositeChargingScheduleLower!.startSchedule!,
+        compositeChargingScheduleLower!.duration!,
       ),
     };
+    const higherFirst = isBefore(
+      compositeChargingScheduleHigherInterval.start,
+      compositeChargingScheduleLowerInterval.start,
+    );
     if (
       !areIntervalsOverlapping(
-        compositeChargingScheduleInterval1,
-        compositeChargingScheduleInterval2,
+        compositeChargingScheduleHigherInterval,
+        compositeChargingScheduleLowerInterval,
       )
     ) {
       return {
-        ...OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1!, targetInterval)!,
-        ...OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2!, targetInterval)!,
+        ...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),
       };
     }
-    // FIXME: Handle overlapping intervals
+    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 = (
@@ -1021,25 +1167,28 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
 
   private static composeChargingSchedule = (
     chargingSchedule: OCPP16ChargingSchedule,
-    targetInterval: Interval,
+    compositeInterval: Interval,
   ): OCPP16ChargingSchedule | undefined => {
     const chargingScheduleInterval: Interval = {
       start: chargingSchedule.startSchedule!,
       end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
     };
-    if (areIntervalsOverlapping(chargingScheduleInterval, targetInterval)) {
+    if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
       chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod);
-      if (isBefore(chargingScheduleInterval.start, targetInterval.start)) {
+      if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
         return {
           ...chargingSchedule,
-          startSchedule: targetInterval.start as Date,
-          duration: differenceInSeconds(chargingScheduleInterval.end, targetInterval.start as Date),
-          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter(
-            (schedulePeriod, index) => {
+          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)!,
-                  targetInterval,
+                  compositeInterval,
                 )
               ) {
                 return true;
@@ -1048,32 +1197,39 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
                 !isWithinInterval(
                   addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
-                  targetInterval,
+                  compositeInterval,
                 ) &&
                 isWithinInterval(
                   addSeconds(
                     chargingScheduleInterval.start,
                     chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod,
                   ),
-                  targetInterval,
+                  compositeInterval,
                 )
               ) {
-                schedulePeriod.startPeriod = 0;
                 return true;
               }
               return false;
-            },
-          ),
+            })
+            .map((schedulePeriod, index) => {
+              if (index === 0 && schedulePeriod.startPeriod !== 0) {
+                schedulePeriod.startPeriod = 0;
+              }
+              return schedulePeriod;
+            }),
         };
       }
-      if (isAfter(chargingScheduleInterval.end, targetInterval.end)) {
+      if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
         return {
           ...chargingSchedule,
-          duration: differenceInSeconds(targetInterval.end as Date, chargingScheduleInterval.start),
+          duration: differenceInSeconds(
+            compositeInterval.end as Date,
+            chargingScheduleInterval.start,
+          ),
           chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter((schedulePeriod) =>
             isWithinInterval(
               addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
-              targetInterval,
+              compositeInterval,
             ),
           ),
         };
@@ -1088,12 +1244,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     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,