import { fileURLToPath } from 'node:url';
import chalk from 'chalk';
-import moment from 'moment';
+import {
+ addDays,
+ addSeconds,
+ addWeeks,
+ differenceInDays,
+ differenceInSeconds,
+ differenceInWeeks,
+ isAfter,
+ isBefore,
+ isWithinInterval,
+ toDate,
+} from 'date-fns';
import type { ChargingStation } from './ChargingStation';
import { BaseError } from '../exception';
Constants,
DCElectricUtils,
cloneObject,
+ convertToDate,
convertToInt,
+ isArraySorted,
isEmptyObject,
isEmptyString,
isNotEmptyArray,
isNotEmptyString,
isNullOrUndefined,
isUndefined,
+ isValidDate,
logger,
secureRandom,
} from '../utils';
export const getChargingStationId = (
index: number,
- stationTemplate: ChargingStationTemplate
+ stationTemplate: ChargingStationTemplate,
): string => {
// In case of multiple instances: add instance index to charging station id
const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0;
return stationTemplate?.fixedName
? stationTemplate.baseName
: `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
- idStr.length - 4
+ idStr.length - 4,
)}${idSuffix}`;
};
export const checkChargingStation = (
chargingStation: ChargingStation,
- logPrefix: string
+ logPrefix: string,
): boolean => {
if (chargingStation.started === false && chargingStation.starting === false) {
logger.warn(`${logPrefix} charging station is stopped, cannot proceed`);
export const getPhaseRotationValue = (
connectorId: number,
- numberOfPhases: number
+ numberOfPhases: number,
): string | undefined => {
// AC/DC
if (connectorId === 0 && numberOfPhases === 0) {
export const getBootConnectorStatus = (
chargingStation: ChargingStation,
connectorId: number,
- connectorStatus: ConnectorStatus
+ connectorStatus: ConnectorStatus,
): ConnectorStatusEnum => {
let connectorBootStatus: ConnectorStatusEnum;
if (
export const checkTemplate = (
stationTemplate: ChargingStationTemplate,
logPrefix: string,
- templateFile: string
+ templateFile: string,
): void => {
if (isNullOrUndefined(stationTemplate)) {
const errorMsg = `Failed to read charging station template file ${templateFile}`;
logger.error(`${logPrefix} ${errorMsg}`);
throw new BaseError(errorMsg);
}
- if (isEmptyObject(stationTemplate.AutomaticTransactionGenerator)) {
+ if (isEmptyObject(stationTemplate.AutomaticTransactionGenerator!)) {
stationTemplate.AutomaticTransactionGenerator = Constants.DEFAULT_ATG_CONFIGURATION;
logger.warn(
`${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
- Constants.DEFAULT_ATG_CONFIGURATION
+ Constants.DEFAULT_ATG_CONFIGURATION,
);
}
if (isNullOrUndefined(stationTemplate.idTagsFile) || isEmptyString(stationTemplate.idTagsFile)) {
logger.warn(
- `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`
+ `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`,
);
}
};
export const checkConnectorsConfiguration = (
stationTemplate: ChargingStationTemplate,
logPrefix: string,
- templateFile: string
+ templateFile: string,
): {
configuredMaxConnectors: number;
templateMaxConnectors: number;
} => {
const configuredMaxConnectors = getConfiguredNumberOfConnectors(stationTemplate);
checkConfiguredMaxConnectors(configuredMaxConnectors, logPrefix, templateFile);
- const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors);
+ const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors!);
checkTemplateMaxConnectors(templateMaxConnectors, logPrefix, templateFile);
- const templateMaxAvailableConnectors = stationTemplate?.Connectors[0]
+ const templateMaxAvailableConnectors = stationTemplate.Connectors![0]
? templateMaxConnectors - 1
: templateMaxConnectors;
if (
!stationTemplate?.randomConnectors
) {
logger.warn(
- `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`
+ `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`,
);
stationTemplate.randomConnectors = true;
}
connectorId: number,
connectorStatus: ConnectorStatus,
logPrefix: string,
- templateFile: string
+ templateFile: string,
): void => {
if (!isNullOrUndefined(connectorStatus?.status)) {
logger.warn(
- `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
+ `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`,
);
delete connectorStatus.status;
}
export const buildConnectorsMap = (
connectors: Record<string, ConnectorStatus>,
logPrefix: string,
- templateFile: string
+ templateFile: string,
): Map<number, ConnectorStatus> => {
const connectorsMap = new Map<number, ConnectorStatus>();
if (getMaxNumberOfConnectors(connectors) > 0) {
}
} else {
logger.warn(
- `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
+ `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`,
);
}
return connectorsMap;
export const initializeConnectorsMapStatus = (
connectors: Map<number, ConnectorStatus>,
- logPrefix: string
+ logPrefix: string,
): void => {
for (const connectorId of connectors.keys()) {
if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
logger.warn(
- `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
- connectors.get(connectorId)?.transactionId
- }`
+ `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${connectors.get(
+ connectorId,
+ )?.transactionId}`,
);
}
if (connectorId === 0) {
- connectors.get(connectorId).availability = AvailabilityType.Operative;
+ connectors.get(connectorId)!.availability = AvailabilityType.Operative;
if (isUndefined(connectors.get(connectorId)?.chargingProfiles)) {
- connectors.get(connectorId).chargingProfiles = [];
+ connectors.get(connectorId)!.chargingProfiles = [];
}
} else if (
connectorId > 0 &&
isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
) {
- initializeConnectorStatus(connectors.get(connectorId));
+ initializeConnectorStatus(connectors.get(connectorId)!);
}
}
};
connectorStatus.idTagAuthorized = false;
connectorStatus.transactionRemoteStarted = false;
connectorStatus.transactionStarted = false;
+ delete connectorStatus?.transactionStart;
+ delete connectorStatus?.transactionId;
delete connectorStatus?.localAuthorizeIdTag;
delete connectorStatus?.authorizeIdTag;
- delete connectorStatus?.transactionId;
delete connectorStatus?.transactionIdTag;
connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
delete connectorStatus?.transactionBeginMeterValue;
export const createBootNotificationRequest = (
stationInfo: ChargingStationInfo,
- bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
+ bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp,
): BootNotificationRequest => {
const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
switch (ocppVersion) {
export const warnTemplateKeysDeprecation = (
stationTemplate: ChargingStationTemplate,
logPrefix: string,
- templateFile: string
+ templateFile: string,
) => {
- const templateKeys: { key: string; deprecatedKey: string }[] = [
- { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' },
- { key: 'idTagsFile', deprecatedKey: 'authorizationFile' },
+ const templateKeys: { deprecatedKey: string; key?: string }[] = [
+ { deprecatedKey: 'supervisionUrl', key: 'supervisionUrls' },
+ { deprecatedKey: 'authorizationFile', key: 'idTagsFile' },
+ { deprecatedKey: 'payloadSchemaValidation', key: 'ocppStrictCompliance' },
];
for (const templateKey of templateKeys) {
warnDeprecatedTemplateKey(
templateKey.deprecatedKey,
logPrefix,
templateFile,
- `Use '${templateKey.key}' instead`
+ !isUndefined(templateKey.key) ? `Use '${templateKey.key}' instead` : undefined,
);
convertDeprecatedTemplateKey(stationTemplate, templateKey.deprecatedKey, templateKey.key);
}
};
export const stationTemplateToStationInfo = (
- stationTemplate: ChargingStationTemplate
+ stationTemplate: ChargingStationTemplate,
): ChargingStationInfo => {
stationTemplate = cloneObject<ChargingStationTemplate>(stationTemplate);
delete stationTemplate.power;
delete stationTemplate.powerUnit;
- delete stationTemplate?.Connectors;
- delete stationTemplate?.Evses;
+ delete stationTemplate.Connectors;
+ delete stationTemplate.Evses;
delete stationTemplate.Configuration;
delete stationTemplate.AutomaticTransactionGenerator;
delete stationTemplate.chargeBoxSerialNumberPrefix;
} = {
randomSerialNumberUpperCase: true,
randomSerialNumber: true,
- }
+ },
): void => {
params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params };
const serialNumberSuffix = params?.randomSerialNumber
export const propagateSerialNumber = (
stationTemplate: ChargingStationTemplate,
stationInfoSrc: ChargingStationInfo,
- stationInfoDst: ChargingStationInfo
+ stationInfoDst: ChargingStationInfo,
) => {
if (!stationInfoSrc || !stationTemplate) {
throw new BaseError(
- 'Missing charging station template or existing configuration to propagate serial number'
+ 'Missing charging station template or existing configuration to propagate serial number',
);
}
stationTemplate?.chargePointSerialNumberPrefix && stationInfoSrc?.chargePointSerialNumber
export const getChargingStationConnectorChargingProfilesPowerLimit = (
chargingStation: ChargingStation,
- connectorId: number
+ connectorId: number,
): number | undefined => {
- let limit: number, matchingChargingProfile: ChargingProfile;
- // Get charging profiles for connector and sort by stack level
+ let limit: number | undefined, matchingChargingProfile: ChargingProfile | undefined;
+ // Get charging profiles for connector id and sort by stack level
const chargingProfiles =
cloneObject<ChargingProfile[]>(
- chargingStation.getConnectorStatus(connectorId)?.chargingProfiles
+ chargingStation.getConnectorStatus(connectorId)!.chargingProfiles!,
)?.sort((a, b) => b.stackLevel - a.stackLevel) ?? [];
- // Get profiles on connector 0
- if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
+ // Get charging profiles on connector 0 and sort by stack level
+ if (isNotEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)) {
chargingProfiles.push(
...cloneObject<ChargingProfile[]>(
- chargingStation.getConnectorStatus(0).chargingProfiles
- ).sort((a, b) => b.stackLevel - a.stackLevel)
+ chargingStation.getConnectorStatus(0)!.chargingProfiles!,
+ ).sort((a, b) => b.stackLevel - a.stackLevel),
);
}
if (isNotEmptyArray(chargingProfiles)) {
- const result = getLimitFromChargingProfiles(chargingProfiles, chargingStation.logPrefix());
+ const result = getLimitFromChargingProfiles(
+ chargingStation,
+ connectorId,
+ chargingProfiles,
+ chargingStation.logPrefix(),
+ );
if (!isNullOrUndefined(result)) {
limit = result?.limit;
matchingChargingProfile = result?.matchingChargingProfile;
switch (chargingStation.getCurrentOutType()) {
case CurrentType.AC:
limit =
- matchingChargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+ matchingChargingProfile?.chargingSchedule?.chargingRateUnit ===
+ ChargingRateUnitType.WATT
? limit
: ACElectricUtils.powerTotal(
chargingStation.getNumberOfPhases(),
chargingStation.getVoltageOut(),
- limit
+ limit!,
);
break;
case CurrentType.DC:
limit =
- matchingChargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+ matchingChargingProfile?.chargingSchedule?.chargingRateUnit ===
+ ChargingRateUnitType.WATT
? limit
- : DCElectricUtils.power(chargingStation.getVoltageOut(), limit);
+ : DCElectricUtils.power(chargingStation.getVoltageOut(), limit!);
}
const connectorMaximumPower =
chargingStation.getMaximumPower() / chargingStation.powerDivider;
- if (limit > connectorMaximumPower) {
+ if (limit! > connectorMaximumPower) {
logger.error(
- `${chargingStation.logPrefix()} Charging profile id ${
- matchingChargingProfile.chargingProfileId
- } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
- result
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${matchingChargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
+ result,
);
limit = connectorMaximumPower;
}
export const getDefaultVoltageOut = (
currentType: CurrentType,
logPrefix: string,
- templateFile: string
+ templateFile: string,
): Voltage => {
const errorMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
let defaultVoltageOut: number;
);
};
-export const waitForChargingStationEvents = async (
+export const waitChargingStationEvents = async (
emitter: EventEmitter,
event: ChargingStationWorkerMessageEvents,
- eventsToWait: number
+ eventsToWait: number,
): Promise<number> => {
- return new Promise((resolve) => {
+ return new Promise<number>((resolve) => {
let events = 0;
if (eventsToWait === 0) {
resolve(events);
};
const getConfiguredNumberOfConnectors = (stationTemplate: ChargingStationTemplate): number => {
- let configuredMaxConnectors: number;
+ let configuredMaxConnectors = 0;
if (isNotEmptyArray(stationTemplate.numberOfConnectors) === true) {
const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
configuredMaxConnectors =
} else if (isUndefined(stationTemplate.numberOfConnectors) === false) {
configuredMaxConnectors = stationTemplate.numberOfConnectors as number;
} else if (stationTemplate.Connectors && !stationTemplate.Evses) {
- configuredMaxConnectors = stationTemplate?.Connectors[0]
+ configuredMaxConnectors = stationTemplate.Connectors[0]
? getMaxNumberOfConnectors(stationTemplate.Connectors) - 1
: getMaxNumberOfConnectors(stationTemplate.Connectors);
} else if (stationTemplate.Evses && !stationTemplate.Connectors) {
- configuredMaxConnectors = 0;
for (const evse in stationTemplate.Evses) {
if (evse === '0') {
continue;
const checkConfiguredMaxConnectors = (
configuredMaxConnectors: number,
logPrefix: string,
- templateFile: string
+ templateFile: string,
): void => {
if (configuredMaxConnectors <= 0) {
logger.warn(
- `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
+ `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`,
);
}
};
const checkTemplateMaxConnectors = (
templateMaxConnectors: number,
logPrefix: string,
- templateFile: string
+ templateFile: string,
): void => {
if (templateMaxConnectors === 0) {
logger.warn(
- `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
+ `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`,
);
} else if (templateMaxConnectors < 0) {
logger.error(
- `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
+ `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`,
);
}
};
key: string,
logPrefix: string,
templateFile: string,
- logMsgToAppend = ''
+ logMsgToAppend = '',
): void => {
- if (!isUndefined(template[key])) {
+ if (!isUndefined(template[key as keyof ChargingStationTemplate])) {
const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
}`;
const convertDeprecatedTemplateKey = (
template: ChargingStationTemplate,
deprecatedKey: string,
- key: string
+ key?: string,
): void => {
- if (!isUndefined(template[deprecatedKey])) {
- template[key] = template[deprecatedKey] as unknown;
- delete template[deprecatedKey];
+ if (!isUndefined(template[deprecatedKey as keyof ChargingStationTemplate])) {
+ if (!isUndefined(key)) {
+ (template as unknown as Record<string, unknown>)[key!] =
+ template[deprecatedKey as keyof ChargingStationTemplate];
+ }
+ delete template[deprecatedKey as keyof ChargingStationTemplate];
}
};
+interface ChargingProfilesLimit {
+ limit: number;
+ matchingChargingProfile: ChargingProfile;
+}
+
/**
- * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
+ * Charging profiles shall already be sorted by connector id and stack level (highest stack level has priority)
*
+ * @param chargingStation -
+ * @param connectorId -
* @param chargingProfiles -
* @param logPrefix -
- * @returns
+ * @returns ChargingProfilesLimit
*/
const getLimitFromChargingProfiles = (
+ chargingStation: ChargingStation,
+ connectorId: number,
chargingProfiles: ChargingProfile[],
- logPrefix: string
-): {
- limit: number;
- matchingChargingProfile: ChargingProfile;
-} | null => {
+ logPrefix: string,
+): ChargingProfilesLimit | undefined => {
const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
- const currentMoment = moment();
const currentDate = new Date();
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId);
for (const chargingProfile of chargingProfiles) {
- // Set helpers
+ 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 (!chargingSchedule?.startSchedule) {
+ if (connectorStatus?.transactionStarted && !chargingSchedule?.startSchedule) {
+ 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 (!(chargingSchedule?.startSchedule instanceof Date)) {
logger.warn(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
+ `${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)!;
}
- // 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 &&
- currentMoment.isAfter(chargingSchedule.startSchedule)
+ isNullOrUndefined(chargingProfile.recurrencyKind)
) {
- if (!(chargingSchedule?.startSchedule instanceof Date)) {
- logger.warn(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
- );
- chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
- }
- chargingSchedule.startSchedule.setFullYear(
- currentDate.getFullYear(),
- currentDate.getMonth(),
- currentDate.getDate()
+ logger.error(
+ `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`,
);
- // Check if the start of the schedule is yesterday
- if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
- chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
- }
- } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
- return null;
+ 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;
+ }
+ if (isNullOrUndefined(chargingSchedule?.duration)) {
+ logger.error(
+ `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`,
+ );
+ continue;
}
// Check if the charging profile is active
if (
- moment(chargingSchedule.startSchedule)
- .add(chargingSchedule.duration, 's')
- .isAfter(currentMoment)
+ isValidDate(chargingSchedule?.startSchedule) &&
+ isWithinInterval(currentDate, {
+ start: chargingSchedule.startSchedule!,
+ end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
+ })
) {
- let lastButOneSchedule: ChargingSchedulePeriod;
- // Search the right schedule period
- for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
- // Handling of only one period
+ if (isNotEmptyArray(chargingSchedule.chargingSchedulePeriod)) {
+ const chargingSchedulePeriodCompareFn = (
+ a: ChargingSchedulePeriod,
+ b: ChargingSchedulePeriod,
+ ) => a.startPeriod - b.startPeriod;
if (
- chargingSchedule.chargingSchedulePeriod.length === 1 &&
- schedulePeriod.startPeriod === 0
+ isArraySorted<ChargingSchedulePeriod>(
+ chargingSchedule.chargingSchedulePeriod,
+ chargingSchedulePeriodCompareFn,
+ ) === false
) {
- const result = {
- limit: schedulePeriod.limit,
- matchingChargingProfile: chargingProfile,
- };
- logger.debug(debugLogMsg, result);
- return result;
+ logger.warn(
+ `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period`,
+ );
+ chargingSchedule.chargingSchedulePeriod.sort(chargingSchedulePeriodCompareFn);
}
- // Find the right schedule period
- if (
- moment(chargingSchedule.startSchedule)
- .add(schedulePeriod.startPeriod, 's')
- .isAfter(currentMoment)
- ) {
- // Found the schedule: last but one is the correct one
- const result = {
- limit: lastButOneSchedule.limit,
- matchingChargingProfile: chargingProfile,
- };
- logger.debug(debugLogMsg, result);
- return result;
+ // Check if the first schedule period start period 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`,
+ );
+ continue;
}
- // Keep it
- lastButOneSchedule = schedulePeriod;
- // Handle the last schedule period
- if (
- schedulePeriod.startPeriod ===
- chargingSchedule.chargingSchedulePeriod[
- chargingSchedule.chargingSchedulePeriod.length - 1
- ].startPeriod
- ) {
- const result = {
- limit: lastButOneSchedule.limit,
+ // Handle only one schedule period
+ if (chargingSchedule.chargingSchedulePeriod.length === 1) {
+ const result: ChargingProfilesLimit = {
+ limit: chargingSchedule.chargingSchedulePeriod[0].limit,
matchingChargingProfile: chargingProfile,
};
logger.debug(debugLogMsg, result);
return result;
}
+ let previousChargingSchedulePeriod: ChargingSchedulePeriod | undefined;
+ // Search for the right schedule period
+ for (const [
+ index,
+ chargingSchedulePeriod,
+ ] of chargingSchedule.chargingSchedulePeriod.entries()) {
+ // Find the right schedule period
+ if (
+ isAfter(
+ addSeconds(chargingSchedule.startSchedule!, chargingSchedulePeriod.startPeriod),
+ currentDate,
+ )
+ ) {
+ // Found the schedule period: previous is the correct one
+ const result: ChargingProfilesLimit = {
+ limit: previousChargingSchedulePeriod!.limit,
+ matchingChargingProfile: chargingProfile,
+ };
+ logger.debug(debugLogMsg, result);
+ return result;
+ }
+ // Keep a reference to previous one
+ previousChargingSchedulePeriod = chargingSchedulePeriod;
+ // Handle the last schedule period within the charging profile duration
+ if (
+ index === chargingSchedule.chargingSchedulePeriod.length - 1 ||
+ (index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
+ chargingSchedule.duration! >
+ differenceInSeconds(
+ addSeconds(
+ chargingSchedule.startSchedule!,
+ chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod,
+ ),
+ chargingSchedule.startSchedule!,
+ ))
+ ) {
+ const result: ChargingProfilesLimit = {
+ limit: previousChargingSchedulePeriod.limit,
+ matchingChargingProfile: chargingProfile,
+ };
+ logger.debug(debugLogMsg, result);
+ return result;
+ }
+ }
}
}
}
- return null;
+};
+
+/**
+ * Adjust recurring charging profile startSchedule to the current recurrency time interval if needed
+ *
+ * @param chargingProfile -
+ * @param currentDate -
+ * @param logPrefix -
+ */
+const prepareRecurringChargingProfile = (
+ chargingProfile: ChargingProfile,
+ currentDate: Date,
+ logPrefix: string,
+): boolean => {
+ const chargingSchedule = chargingProfile.chargingSchedule;
+ let recurringIntervalTranslated = false;
+ let recurringInterval: Interval;
+ switch (chargingProfile.recurrencyKind) {
+ case RecurrencyKindType.DAILY:
+ recurringInterval = {
+ start: chargingSchedule.startSchedule!,
+ end: addDays(chargingSchedule.startSchedule!, 1),
+ };
+ checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix);
+ if (
+ !isWithinInterval(currentDate, recurringInterval) &&
+ isBefore(recurringInterval.end, currentDate)
+ ) {
+ chargingSchedule.startSchedule = addDays(
+ recurringInterval.start,
+ differenceInDays(currentDate, recurringInterval.start),
+ );
+ recurringInterval = {
+ start: chargingSchedule.startSchedule,
+ end: addDays(chargingSchedule.startSchedule, 1),
+ };
+ recurringIntervalTranslated = true;
+ }
+ break;
+ case RecurrencyKindType.WEEKLY:
+ recurringInterval = {
+ start: chargingSchedule.startSchedule!,
+ end: addWeeks(chargingSchedule.startSchedule!, 1),
+ };
+ checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix);
+ if (
+ !isWithinInterval(currentDate, recurringInterval) &&
+ isBefore(recurringInterval.end, currentDate)
+ ) {
+ chargingSchedule.startSchedule = addWeeks(
+ recurringInterval.start,
+ differenceInWeeks(currentDate, recurringInterval.start),
+ );
+ recurringInterval = {
+ start: chargingSchedule.startSchedule,
+ end: addWeeks(chargingSchedule.startSchedule, 1),
+ };
+ recurringIntervalTranslated = true;
+ }
+ break;
+ default:
+ logger.error(
+ `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} recurrency kind ${chargingProfile.recurrencyKind} is not supported`,
+ );
+ }
+ if (recurringIntervalTranslated && !isWithinInterval(currentDate, recurringInterval!)) {
+ logger.error(
+ `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${
+ chargingProfile.recurrencyKind
+ } charging profile id ${chargingProfile.chargingProfileId} recurrency time interval [${toDate(
+ recurringInterval!.start,
+ ).toISOString()}, ${toDate(
+ recurringInterval!.end,
+ ).toISOString()}] has not been properly translated to current date ${currentDate.toISOString()} `,
+ );
+ }
+ return recurringIntervalTranslated;
+};
+
+const checkRecurringChargingProfileDuration = (
+ chargingProfile: ChargingProfile,
+ interval: Interval,
+ logPrefix: string,
+): void => {
+ if (isNullOrUndefined(chargingProfile.chargingSchedule.duration)) {
+ logger.warn(
+ `${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${
+ chargingProfile.chargingProfileKind
+ } charging profile id ${
+ chargingProfile.chargingProfileId
+ } duration is not defined, set it to the recurrency time interval duration ${differenceInSeconds(
+ interval.end,
+ interval.start,
+ )}`,
+ );
+ chargingProfile.chargingSchedule.duration = differenceInSeconds(interval.end, interval.start);
+ } else if (
+ chargingProfile.chargingSchedule.duration! > differenceInSeconds(interval.end, interval.start)
+ ) {
+ logger.warn(
+ `${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${
+ chargingProfile.chargingProfileKind
+ } charging profile id ${chargingProfile.chargingProfileId} duration ${
+ chargingProfile.chargingSchedule.duration
+ } is greater than the recurrency time interval duration ${differenceInSeconds(
+ interval.end,
+ interval.start,
+ )}`,
+ );
+ chargingProfile.chargingSchedule.duration = differenceInSeconds(interval.end, interval.start);
+ }
};
const getRandomSerialNumberSuffix = (params?: {