From 1e5bbb96f70040b92a6e49c73a32e1eb8bd1bcc9 Mon Sep 17 00:00:00 2001 From: OliveGerste Date: Fri, 6 May 2022 12:52:08 +0200 Subject: [PATCH] Extended charging profile support --- src/charging-station/ChargingStation.ts | 178 +++++++++++++++--------- 1 file changed, 113 insertions(+), 65 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index e5759c33..82db1976 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -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; + } } -- 2.34.1