Extended charging profile support
authorOliveGerste <tofle374@gmail.com>
Fri, 6 May 2022 10:52:08 +0000 (12:52 +0200)
committerOliveGerste <tofle374@gmail.com>
Fri, 6 May 2022 10:52:08 +0000 (12:52 +0200)
src/charging-station/ChargingStation.ts

index e5759c33be3a2114e0df6c7eb04131cdcafb4ff7..82db1976e13a1d7c05509ab12abf78bce73a877f 100644 (file)
@@ -26,6 +26,7 @@ import {
   ChargingRateUnitType,
   ChargingSchedulePeriod,
 } from '../types/ocpp/ChargingProfile';
+import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile';
 import ChargingStationConfiguration, { Section } from '../types/ChargingStationConfiguration';
 import ChargingStationOcppConfiguration, {
   ConfigurationKey,
@@ -82,6 +83,7 @@ import Utils from '../utils/Utils';
 import crypto from 'crypto';
 import fs from 'fs';
 import logger from '../utils/Logger';
+import moment from 'moment';
 import { parentPort } from 'worker_threads';
 import path from 'path';
 
@@ -738,80 +740,60 @@ export default class ChargingStation {
   }
 
   public getChargingProfilePowerLimit(connectorId: number): number | undefined {
-    const timestamp = new Date().getTime();
+    let limit: number;
     let matchingChargingProfile: ChargingProfile;
-    let chargingSchedulePeriods: ChargingSchedulePeriod[] = [];
-    if (!Utils.isEmptyArray(this.getConnectorStatus(connectorId)?.chargingProfiles)) {
-      const chargingProfiles: ChargingProfile[] = this.getConnectorStatus(
-        connectorId
-      ).chargingProfiles.filter(
-        (chargingProfile) =>
-          timestamp >= chargingProfile.chargingSchedule?.startSchedule.getTime() &&
-          timestamp <
-            chargingProfile.chargingSchedule?.startSchedule.getTime() +
-              chargingProfile.chargingSchedule.duration * 1000 &&
-          chargingProfile?.stackLevel === Math.max(...chargingProfiles.map((cp) => cp?.stackLevel))
+    let chargingProfiles: ChargingProfile[] = [];
+    // Get profiles for connector and sort by stack level
+    chargingProfiles = this.getConnectorStatus(connectorId).chargingProfiles.sort(
+      (a, b) => b.stackLevel - a.stackLevel
+    );
+    // Get profiles on connector 0
+    if (this.getConnectorStatus(0).chargingProfiles) {
+      chargingProfiles.push(
+        ...this.getConnectorStatus(0).chargingProfiles.sort((a, b) => b.stackLevel - a.stackLevel)
       );
-      if (!Utils.isEmptyArray(chargingProfiles)) {
-        for (const chargingProfile of chargingProfiles) {
-          if (!Utils.isEmptyArray(chargingProfile.chargingSchedule.chargingSchedulePeriod)) {
-            chargingSchedulePeriods =
-              chargingProfile.chargingSchedule.chargingSchedulePeriod.filter(
-                (chargingSchedulePeriod, index) => {
-                  timestamp >=
-                    chargingProfile.chargingSchedule.startSchedule.getTime() +
-                      chargingSchedulePeriod.startPeriod * 1000 &&
-                    ((chargingProfile.chargingSchedule.chargingSchedulePeriod[index + 1] &&
-                      timestamp <
-                        chargingProfile.chargingSchedule.startSchedule.getTime() +
-                          chargingProfile.chargingSchedule.chargingSchedulePeriod[index + 1]
-                            ?.startPeriod *
-                            1000) ||
-                      !chargingProfile.chargingSchedule.chargingSchedulePeriod[index + 1]);
-                }
-              );
-            if (!Utils.isEmptyArray(chargingSchedulePeriods)) {
-              matchingChargingProfile = chargingProfile;
-              break;
-            }
-          }
-        }
-      }
     }
-    let limit: number;
-    if (!Utils.isEmptyArray(chargingSchedulePeriods)) {
-      switch (this.getCurrentOutType()) {
-        case CurrentType.AC:
-          limit =
-            matchingChargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
-              ? chargingSchedulePeriods[0].limit
-              : ACElectricUtils.powerTotal(
-                  this.getNumberOfPhases(),
-                  this.getVoltageOut(),
-                  chargingSchedulePeriods[0].limit
-                );
-          break;
-        case CurrentType.DC:
-          limit =
-            matchingChargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
-              ? chargingSchedulePeriods[0].limit
-              : DCElectricUtils.power(this.getVoltageOut(), chargingSchedulePeriods[0].limit);
+    if (!Utils.isEmptyArray(chargingProfiles)) {
+      const result = this.getCurrentLimitFromOCPPProfiles(chargingProfiles);
+      if (!Utils.isNullOrUndefined(result)) {
+        limit = result.limit;
+        matchingChargingProfile = result.matchingChargingProfile;
+        switch (this.getCurrentOutType()) {
+          case CurrentType.AC:
+            limit =
+              matchingChargingProfile.chargingSchedule.chargingRateUnit ===
+              ChargingRateUnitType.WATT
+                ? limit
+                : ACElectricUtils.powerTotal(this.getNumberOfPhases(), this.getVoltageOut(), limit);
+            break;
+          case CurrentType.DC:
+            limit =
+              matchingChargingProfile.chargingSchedule.chargingRateUnit ===
+              ChargingRateUnitType.WATT
+                ? limit
+                : DCElectricUtils.power(this.getVoltageOut(), limit);
+        }
+
+        const connectorMaximumPower = this.getMaximumPower() / this.stationInfo.powerDivider;
+        if (limit > connectorMaximumPower) {
+          logger.error(
+            `${this.logPrefix()} Charging profile id ${
+              matchingChargingProfile.chargingProfileId
+            } limit is greater than connector id ${connectorId} maximum, dump charging profiles' stack: %j`,
+            this.getConnectorStatus(connectorId).chargingProfiles
+          );
+          limit = connectorMaximumPower;
+        }
       }
     }
-    const connectorMaximumPower = this.getMaximumPower() / this.stationInfo.powerDivider;
-    if (limit > connectorMaximumPower) {
-      logger.error(
-        `${this.logPrefix()} Charging profile id ${
-          matchingChargingProfile.chargingProfileId
-        } limit is greater than connector id ${connectorId} maximum, dump charging profiles' stack: %j`,
-        this.getConnectorStatus(connectorId).chargingProfiles
-      );
-      limit = connectorMaximumPower;
-    }
     return limit;
   }
 
   public setChargingProfile(connectorId: number, cp: ChargingProfile): void {
+    // Add support for connectorID 0
+    if (!Array.isArray(this.getConnectorStatus(connectorId).chargingProfiles)) {
+      this.getConnectorStatus(connectorId).chargingProfiles = [];
+    }
     let cpReplaced = false;
     if (!Utils.isEmptyArray(this.getConnectorStatus(connectorId).chargingProfiles)) {
       this.getConnectorStatus(connectorId).chargingProfiles?.forEach(
@@ -2248,4 +2230,70 @@ export default class ChargingStation {
     this.getConnectorStatus(connectorId).energyActiveImportRegisterValue = 0;
     this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;
   }
+
+  private getCurrentLimitFromOCPPProfiles(chargingProfiles: ChargingProfile[]): {
+    limit: number;
+    matchingChargingProfile: ChargingProfile;
+  } {
+    for (const chargingProfile of chargingProfiles) {
+      // Profiles should already be sorted by connectorID and Stack Level (highest stack level has prio)
+      // Set helpers
+      const now = moment();
+      const chargingSchedule = chargingProfile.chargingSchedule;
+      // Check type (Recurring) and if it is already active
+      // Adjust the Daily Recurring Schedule to today
+      if (
+        chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
+        chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
+        now.isAfter(chargingSchedule.startSchedule)
+      ) {
+        const currentDate = new Date();
+        chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
+        chargingSchedule.startSchedule.setFullYear(
+          currentDate.getFullYear(),
+          currentDate.getMonth(),
+          currentDate.getDate()
+        );
+        // Check if the start of the schedule is yesterday
+        if (moment(chargingSchedule.startSchedule).isAfter(now)) {
+          chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
+        }
+      } else if (moment(chargingSchedule.startSchedule).isAfter(now)) {
+        return null;
+      }
+      // Check if the Charging Profile is active
+      if (moment(chargingSchedule.startSchedule).add(chargingSchedule.duration, 's').isAfter(now)) {
+        let lastButOneSchedule: ChargingSchedulePeriod;
+        // Search the right Schedule Period
+        for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
+          // Handling of only one period
+          if (
+            chargingSchedule.chargingSchedulePeriod.length === 1 &&
+            schedulePeriod.startPeriod === 0
+          ) {
+            return { limit: schedulePeriod.limit, matchingChargingProfile: chargingProfile };
+          }
+          // Find the right Schedule Periods
+          if (
+            moment(chargingSchedule.startSchedule).add(schedulePeriod.startPeriod, 's').isAfter(now)
+          ) {
+            // Found the schedule: Last but one is the correct one
+            return { limit: lastButOneSchedule.limit, matchingChargingProfile: chargingProfile };
+          }
+          // Keep it
+          lastButOneSchedule = schedulePeriod;
+          // Handle the last schedule period
+          if (
+            schedulePeriod.startPeriod ===
+            chargingSchedule.chargingSchedulePeriod[
+              chargingSchedule.chargingSchedulePeriod.length - 1
+            ].startPeriod
+          ) {
+            return { limit: lastButOneSchedule.limit, matchingChargingProfile: chargingProfile };
+          }
+        }
+      }
+    }
+    return null;
+  }
 }