refactor: factor out ATG and charging profiles sanity checks
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 26 Jul 2023 21:29:23 +0000 (23:29 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 26 Jul 2023 21:29:23 +0000 (23:29 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStationUtils.ts
src/utils/Utils.ts
src/utils/index.ts
test/utils/Utils.test.ts

index 5b27a258c9e2e73d0424f04c8d68e6c7cc2ec24c..5ed5be42e83237067ee4d32b766e643cc9cdba71 100644 (file)
@@ -190,46 +190,7 @@ export class AutomaticTransactionGenerator extends AsyncResource {
       )}`,
     );
     while (this.connectorsStatus.get(connectorId)?.start === true) {
-      if (new Date() > this.connectorsStatus.get(connectorId)!.stopDate!) {
-        this.stopConnector(connectorId);
-        break;
-      }
-      if (this.chargingStation.inAcceptedState() === false) {
-        logger.error(
-          `${this.logPrefix(
-            connectorId,
-          )} entered in transaction loop while the charging station is not in accepted state`,
-        );
-        this.stopConnector(connectorId);
-        break;
-      }
-      if (this.chargingStation.isChargingStationAvailable() === false) {
-        logger.info(
-          `${this.logPrefix(
-            connectorId,
-          )} entered in transaction loop while the charging station is unavailable`,
-        );
-        this.stopConnector(connectorId);
-        break;
-      }
-      if (this.chargingStation.isConnectorAvailable(connectorId) === false) {
-        logger.info(
-          `${this.logPrefix(
-            connectorId,
-          )} entered in transaction loop while the connector ${connectorId} is unavailable`,
-        );
-        this.stopConnector(connectorId);
-        break;
-      }
-      if (
-        this.chargingStation.getConnectorStatus(connectorId)?.status ===
-        ConnectorStatusEnum.Unavailable
-      ) {
-        logger.info(
-          `${this.logPrefix(
-            connectorId,
-          )} entered in transaction loop while the connector ${connectorId} status is unavailable`,
-        );
+      if (!this.canStartConnector(connectorId)) {
         this.stopConnector(connectorId);
         break;
       }
@@ -332,6 +293,48 @@ export class AutomaticTransactionGenerator extends AsyncResource {
     this.connectorsStatus.get(connectorId)!.start = true;
   }
 
+  private canStartConnector(connectorId: number): boolean {
+    if (new Date() > this.connectorsStatus.get(connectorId)!.stopDate!) {
+      return false;
+    }
+    if (this.chargingStation.inAcceptedState() === false) {
+      logger.error(
+        `${this.logPrefix(
+          connectorId,
+        )} entered in transaction loop while the charging station is not in accepted state`,
+      );
+      return false;
+    }
+    if (this.chargingStation.isChargingStationAvailable() === false) {
+      logger.info(
+        `${this.logPrefix(
+          connectorId,
+        )} entered in transaction loop while the charging station is unavailable`,
+      );
+      return false;
+    }
+    if (this.chargingStation.isConnectorAvailable(connectorId) === false) {
+      logger.info(
+        `${this.logPrefix(
+          connectorId,
+        )} entered in transaction loop while the connector ${connectorId} is unavailable`,
+      );
+      return false;
+    }
+    if (
+      this.chargingStation.getConnectorStatus(connectorId)?.status ===
+      ConnectorStatusEnum.Unavailable
+    ) {
+      logger.info(
+        `${this.logPrefix(
+          connectorId,
+        )} entered in transaction loop while the connector ${connectorId} status is unavailable`,
+      );
+      return false;
+    }
+    return true;
+  }
+
   private initializeConnectorsStatus(): void {
     if (this.chargingStation.hasEvses) {
       for (const [evseId, evseStatus] of this.chargingStation.evses) {
index b35f0260ad78d84058dc317797aebb67f66a5c8e..39bcbcc987991d7e082e2b5fc9934591222522f1 100644 (file)
@@ -13,6 +13,7 @@ import {
   differenceInWeeks,
   isAfter,
   isBefore,
+  isDate,
   isWithinInterval,
   toDate,
 } from 'date-fns';
@@ -56,7 +57,7 @@ import {
   isNotEmptyString,
   isNullOrUndefined,
   isUndefined,
-  isValidDate,
+  isValidTime,
   logger,
   secureRandom,
 } from '../utils';
@@ -690,18 +691,6 @@ const getLimitFromChargingProfiles = (
   const currentDate = new Date();
   const connectorStatus = chargingStation.getConnectorStatus(connectorId);
   for (const chargingProfile of chargingProfiles) {
-    if (
-      (isValidDate(chargingProfile.validFrom) &&
-        isBefore(currentDate, chargingProfile.validFrom!)) ||
-      (isValidDate(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo!))
-    ) {
-      logger.debug(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${
-          chargingProfile.chargingProfileId
-        } is not valid for the current date ${currentDate.toISOString()}`,
-      );
-      continue;
-    }
     const chargingSchedule = chargingProfile.chargingSchedule;
     if (connectorStatus?.transactionStarted && isNullOrUndefined(chargingSchedule?.startSchedule)) {
       logger.debug(
@@ -710,44 +699,30 @@ const getLimitFromChargingProfiles = (
       // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
       chargingSchedule.startSchedule = connectorStatus?.transactionStart;
     }
-    if (!(chargingSchedule?.startSchedule instanceof Date)) {
+    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`,
       );
       chargingSchedule.startSchedule = convertToDate(chargingSchedule?.startSchedule)!;
     }
-    if (
-      chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
-      isNullOrUndefined(chargingProfile.recurrencyKind)
-    ) {
-      logger.error(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`,
-      );
-      continue;
-    }
-    if (chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING) {
-      prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix);
-    } else if (
-      chargingProfile.chargingProfileKind === ChargingProfileKindType.RELATIVE &&
-      connectorStatus?.transactionStarted
-    ) {
-      chargingSchedule.startSchedule = connectorStatus?.transactionStart;
-    }
-    if (isNullOrUndefined(chargingSchedule?.startSchedule)) {
-      logger.error(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has (still) no startSchedule defined`,
-      );
-      continue;
+    switch (chargingProfile.chargingProfileKind) {
+      case ChargingProfileKindType.RECURRING:
+        if (!canProceedRecurringChargingProfile(chargingProfile, logPrefix)) {
+          continue;
+        }
+        prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix);
+        break;
+      case ChargingProfileKindType.RELATIVE:
+        connectorStatus?.transactionStarted &&
+          (chargingSchedule.startSchedule = connectorStatus?.transactionStart);
+        break;
     }
-    if (isNullOrUndefined(chargingSchedule?.duration)) {
-      logger.error(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`,
-      );
+    if (!canProceedChargingProfile(chargingProfile, currentDate, logPrefix)) {
       continue;
     }
     // Check if the charging profile is active
     if (
-      isValidDate(chargingSchedule?.startSchedule) &&
+      isValidTime(chargingSchedule?.startSchedule) &&
       isWithinInterval(currentDate, {
         start: chargingSchedule.startSchedule!,
         end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
@@ -834,6 +809,54 @@ const getLimitFromChargingProfiles = (
   }
 };
 
+const canProceedChargingProfile = (
+  chargingProfile: ChargingProfile,
+  currentDate: Date,
+  logPrefix: string,
+): boolean => {
+  if (
+    (isValidTime(chargingProfile.validFrom) && isBefore(currentDate, chargingProfile.validFrom!)) ||
+    (isValidTime(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo!))
+  ) {
+    logger.debug(
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${
+        chargingProfile.chargingProfileId
+      } is not valid for the current date ${currentDate.toISOString()}`,
+    );
+    return false;
+  }
+  const chargingSchedule = chargingProfile.chargingSchedule;
+  if (isNullOrUndefined(chargingSchedule?.startSchedule)) {
+    logger.error(
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has (still) no startSchedule defined`,
+    );
+    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;
+};
+
+const canProceedRecurringChargingProfile = (
+  chargingProfile: ChargingProfile,
+  logPrefix: string,
+): boolean => {
+  if (
+    chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
+    isNullOrUndefined(chargingProfile.recurrencyKind)
+  ) {
+    logger.error(
+      `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`,
+    );
+    return false;
+  }
+  return true;
+};
+
 /**
  * Adjust recurring charging profile startSchedule to the current recurrency time interval if needed
  *
index dd6591f88bd2b27fe999a2e9c748a5029eb41383..73bb0ba927d2edce72ddf493635915a461d85051 100644 (file)
@@ -60,8 +60,8 @@ export const formatDurationSeconds = (duration: number): string => {
   return formatDurationMilliSeconds(secondsToMilliseconds(duration));
 };
 
-// More efficient date validation function than the one provided by date-fns
-export const isValidDate = (date: unknown): boolean => {
+// More efficient time validation function than the one provided by date-fns
+export const isValidTime = (date: unknown): boolean => {
   if (typeof date === 'number') {
     return !isNaN(date);
   } else if (isDate(date)) {
@@ -76,8 +76,8 @@ export const convertToDate = (
   if (isNullOrUndefined(value)) {
     return value as null | undefined;
   }
-  if (value instanceof Date) {
-    return value;
+  if (isDate(value)) {
+    return value as Date;
   }
   if (isString(value) || typeof value === 'number') {
     return new Date(value!);
index 86649874a30e1434c751056257972535fb760be4..1cdd45ee37c41b0b47591f7bf4dc13c6bd16c793 100644 (file)
@@ -47,7 +47,7 @@ export {
   isNotEmptyString,
   isNullOrUndefined,
   isUndefined,
-  isValidDate,
+  isValidTime,
   logPrefix,
   promiseWithTimeout,
   roundTo,
index 0050f65bf42f24f229b5671d9be2daf8c3d725fa..67da558e326169b4969744dd1ac7a8d38efbf4d6 100644 (file)
@@ -24,7 +24,7 @@ import {
   isNullOrUndefined,
   isObject,
   isUndefined,
-  isValidDate,
+  isValidTime,
   roundTo,
   secureRandom,
   sleep,
@@ -63,22 +63,22 @@ describe('Utils test suite', () => {
     expect(formatDurationSeconds(hoursToSeconds(4380))).toBe('182 days 12 hours');
   });
 
-  it('Verify isValidDate()', () => {
-    expect(isValidDate(undefined)).toBe(false);
-    expect(isValidDate(null)).toBe(false);
-    expect(isValidDate('')).toBe(false);
-    expect(isValidDate({})).toBe(false);
-    expect(isValidDate([])).toBe(false);
-    expect(isValidDate(new Map())).toBe(false);
-    expect(isValidDate(new Set())).toBe(false);
-    expect(isValidDate(new WeakMap())).toBe(false);
-    expect(isValidDate(new WeakSet())).toBe(false);
-    expect(isValidDate(-1)).toBe(true);
-    expect(isValidDate(0)).toBe(true);
-    expect(isValidDate(1)).toBe(true);
-    expect(isValidDate(-0.5)).toBe(true);
-    expect(isValidDate(0.5)).toBe(true);
-    expect(isValidDate(new Date())).toBe(true);
+  it('Verify isValidTime()', () => {
+    expect(isValidTime(undefined)).toBe(false);
+    expect(isValidTime(null)).toBe(false);
+    expect(isValidTime('')).toBe(false);
+    expect(isValidTime({})).toBe(false);
+    expect(isValidTime([])).toBe(false);
+    expect(isValidTime(new Map())).toBe(false);
+    expect(isValidTime(new Set())).toBe(false);
+    expect(isValidTime(new WeakMap())).toBe(false);
+    expect(isValidTime(new WeakSet())).toBe(false);
+    expect(isValidTime(-1)).toBe(true);
+    expect(isValidTime(0)).toBe(true);
+    expect(isValidTime(1)).toBe(true);
+    expect(isValidTime(-0.5)).toBe(true);
+    expect(isValidTime(0.5)).toBe(true);
+    expect(isValidTime(new Date())).toBe(true);
   });
 
   it('Verify convertToDate()', () => {