fix: avoid gaps in get composite schedule
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 2 Aug 2023 18:16:55 +0000 (20:16 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 2 Aug 2023 18:16:55 +0000 (20:16 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/Helpers.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts

index 63cc0f634e7b2bc6e066b126a2c63911030bb774..b722926205f0d5b949ae450a92990abfe86e971b 100644 (file)
@@ -16,6 +16,7 @@ import {
   isDate,
   isPast,
   isWithinInterval,
+  maxTime,
   toDate,
 } from 'date-fns';
 
@@ -753,13 +754,23 @@ const getLimitFromChargingProfiles = (
   const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
   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`,
@@ -796,7 +807,7 @@ const getLimitFromChargingProfiles = (
           );
           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`,
@@ -910,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;
 };
 
index 66a6ee5960a95b7050a1888122e9bcef4cfe7a24..d24a2d4cd36e2716ab35b375af7b433017e4a9f3 100644 (file)
@@ -712,8 +712,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     const chargingProfiles: OCPP16ChargingProfile[] = [];
     for (const storedChargingProfile of storedChargingProfiles) {
       if (
-        connectorStatus?.transactionStarted &&
-        isNullOrUndefined(storedChargingProfile.chargingSchedule?.startSchedule)
+        isNullOrUndefined(storedChargingProfile.chargingSchedule?.startSchedule) &&
+        connectorStatus?.transactionStarted
       ) {
         logger.debug(
           `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
@@ -723,6 +723,21 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
         storedChargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart;
       }
+      if (
+        !isNullOrUndefined(storedChargingProfile.chargingSchedule?.startSchedule) &&
+        isNullOrUndefined(storedChargingProfile.chargingSchedule?.duration)
+      ) {
+        logger.debug(
+          `${chargingStation.logPrefix()} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${
+            storedChargingProfile.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
+        storedChargingProfile.chargingSchedule.duration = differenceInSeconds(
+          maxTime,
+          storedChargingProfile.chargingSchedule.startSchedule!,
+        );
+      }
       if (!isDate(storedChargingProfile.chargingSchedule?.startSchedule)) {
         logger.warn(
           `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
@@ -837,20 +852,52 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
             )
           ) {
             // Remove charging schedule periods that are before the end of the active profiles interval
-            // FIXME: can lead to a gap in the charging schedule: chargingProfilesInterval.end -> first matching schedulePeriod.startPeriod
             storedChargingProfile.chargingSchedule.chargingSchedulePeriod =
               storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter(
-                (schedulePeriod) =>
-                  isWithinInterval(
-                    addSeconds(
-                      storedChargingProfile.chargingSchedule.startSchedule!,
-                      schedulePeriod.startPeriod,
-                    ),
-                    {
-                      start: chargingProfilesInterval.end,
-                      end: interval.end,
-                    },
-                  ),
+                (schedulePeriod, index) => {
+                  if (
+                    isWithinInterval(
+                      addSeconds(
+                        storedChargingProfile.chargingSchedule.startSchedule!,
+                        schedulePeriod.startPeriod,
+                      ),
+                      {
+                        start: chargingProfilesInterval.end,
+                        end: interval.end,
+                      },
+                    )
+                  ) {
+                    return true;
+                  }
+                  if (
+                    !isWithinInterval(
+                      addSeconds(
+                        storedChargingProfile.chargingSchedule.startSchedule!,
+                        schedulePeriod.startPeriod,
+                      ),
+                      {
+                        start: chargingProfilesInterval.end,
+                        end: interval.end,
+                      },
+                    ) &&
+                    index <
+                      storedChargingProfile.chargingSchedule.chargingSchedulePeriod.length - 1 &&
+                    isWithinInterval(
+                      addSeconds(
+                        storedChargingProfile.chargingSchedule.startSchedule!,
+                        storedChargingProfile.chargingSchedule.chargingSchedulePeriod[index + 1]
+                          .startPeriod,
+                      ),
+                      {
+                        start: chargingProfilesInterval.end,
+                        end: interval.end,
+                      },
+                    )
+                  ) {
+                    return true;
+                  }
+                  return false;
+                },
               );
             addChargingProfile = true;
           }