fix: avoid gaps in get composite schedule
[e-mobility-charging-stations-simulator.git] / src / charging-station / Helpers.ts
index f263daa473be009ae0f5c4b980ed0b137012e2da..b722926205f0d5b949ae450a92990abfe86e971b 100644 (file)
@@ -16,6 +16,7 @@ import {
   isDate,
   isPast,
   isWithinInterval,
+  maxTime,
   toDate,
 } from 'date-fns';
 
@@ -514,17 +515,36 @@ export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInf
   return unitDivider;
 };
 
+/**
+ * Gets the connector cloned charging profiles applying a power limitation
+ * and sorted by connector id ascending then stack level descending
+ *
+ * @param chargingStation -
+ * @param connectorId -
+ * @returns connector charging profiles array
+ */
+export const getConnectorChargingProfiles = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+) => {
+  return cloneObject<ChargingProfile[]>(
+    (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [])
+      .sort((a, b) => b.stackLevel - a.stackLevel)
+      .concat(
+        (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? []).sort(
+          (a, b) => b.stackLevel - a.stackLevel,
+        ),
+      ),
+  );
+};
+
 export const getChargingStationConnectorChargingProfilesPowerLimit = (
   chargingStation: ChargingStation,
   connectorId: number,
 ): number | undefined => {
   let limit: number | undefined, chargingProfile: ChargingProfile | undefined;
-  // Get charging profiles for connector id and sort by stack level
-  const chargingProfiles = cloneObject<ChargingProfile[]>(
-    (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? []).concat(
-      chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [],
-    ),
-  ).sort((a, b) => b.stackLevel - a.stackLevel);
+  // Get charging profiles sorted by connector id then stack level
+  const chargingProfiles = getConnectorChargingProfiles(chargingStation, connectorId);
   if (isNotEmptyArray(chargingProfiles)) {
     const result = getLimitFromChargingProfiles(
       chargingStation,
@@ -715,7 +735,7 @@ interface ChargingProfilesLimit {
 }
 
 /**
- * Charging profiles shall already be sorted by connector id and stack level (highest stack level has priority)
+ * Charging profiles shall already be sorted by connector id ascending then stack level descending
  *
  * @param chargingStation -
  * @param connectorId -
@@ -732,21 +752,25 @@ const getLimitFromChargingProfiles = (
   const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
   const currentDate = new Date();
   const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
-  if (!isArraySorted(chargingProfiles, (a, b) => b.stackLevel - a.stackLevel)) {
-    logger.warn(
-      `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profiles are not sorted by stack level. Trying to sort them`,
-    );
-    chargingProfiles.sort((a, b) => b.stackLevel - a.stackLevel);
-  }
   for (const chargingProfile of chargingProfiles) {
     const chargingSchedule = chargingProfile.chargingSchedule;
-    if (connectorStatus?.transactionStarted && isNullOrUndefined(chargingSchedule?.startSchedule)) {
+    if (isNullOrUndefined(chargingSchedule?.startSchedule) && connectorStatus?.transactionStarted) {
       logger.debug(
         `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined. Trying to set it to the connector current transaction start date`,
       );
       // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
       chargingSchedule.startSchedule = connectorStatus?.transactionStart;
     }
+    if (
+      !isNullOrUndefined(chargingSchedule?.startSchedule) &&
+      isNullOrUndefined(chargingSchedule?.duration)
+    ) {
+      logger.debug(
+        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined and will be set to the maximum time allowed`,
+      );
+      // OCPP specifies that if duration is not defined, it should be infinite
+      chargingSchedule.duration = differenceInSeconds(maxTime, chargingSchedule.startSchedule!);
+    }
     if (!isDate(chargingSchedule?.startSchedule)) {
       logger.warn(
         `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date object. Trying to convert it to a Date object`,
@@ -773,17 +797,17 @@ const getLimitFromChargingProfiles = (
           b: ChargingSchedulePeriod,
         ) => a.startPeriod - b.startPeriod;
         if (
-          isArraySorted<ChargingSchedulePeriod>(
+          !isArraySorted<ChargingSchedulePeriod>(
             chargingSchedule.chargingSchedulePeriod,
             chargingSchedulePeriodCompareFn,
-          ) === false
+          )
         ) {
           logger.warn(
             `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period`,
           );
           chargingSchedule.chargingSchedulePeriod.sort(chargingSchedulePeriodCompareFn);
         }
-        // Check if the first schedule period start period is equal to 0
+        // Check if the first schedule period startPeriod property is equal to 0
         if (chargingSchedule.chargingSchedulePeriod[0].startPeriod !== 0) {
           logger.error(
             `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod} is not equal to 0`,
@@ -815,7 +839,7 @@ const getLimitFromChargingProfiles = (
             // Found the schedule period: previous is the correct one
             const result: ChargingProfilesLimit = {
               limit: previousChargingSchedulePeriod!.limit,
-              chargingProfile: chargingProfile,
+              chargingProfile,
             };
             logger.debug(debugLogMsg, result);
             return result;
@@ -836,7 +860,7 @@ const getLimitFromChargingProfiles = (
           ) {
             const result: ChargingProfilesLimit = {
               limit: previousChargingSchedulePeriod.limit,
-              chargingProfile: chargingProfile,
+              chargingProfile,
             };
             logger.debug(debugLogMsg, result);
             return result;
@@ -863,7 +887,7 @@ export const prepareChargingProfileKind = (
     case ChargingProfileKindType.RELATIVE:
       if (!isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule)) {
         logger.warn(
-          `${logPrefix} ${moduleName}.prepareChargingProfileKind: Charging profile id ${chargingProfile.chargingProfileId} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`,
+          `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfile.chargingProfileId} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`,
         );
         delete chargingProfile.chargingSchedule.startSchedule;
       }
@@ -897,12 +921,6 @@ export const canProceedChargingProfile = (
     );
     return false;
   }
-  if (isNullOrUndefined(chargingSchedule?.duration)) {
-    logger.error(
-      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`,
-    );
-    return false;
-  }
   return true;
 };
 
@@ -982,7 +1000,7 @@ const prepareRecurringChargingProfile = (
       break;
     default:
       logger.error(
-        `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} recurrency kind ${chargingProfile.recurrencyKind} is not supported`,
+        `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${chargingProfile.recurrencyKind} charging profile id ${chargingProfile.chargingProfileId} is not supported`,
       );
   }
   if (recurringIntervalTranslated && !isWithinInterval(currentDate, recurringInterval!)) {