import type { JSONSchemaType } from 'ajv';
import { Client, type FTPResponse } from 'basic-ftp';
-import {
- addSeconds,
- isDate,
- isWithinInterval,
- maxTime,
- min,
- secondsToMilliseconds,
-} from 'date-fns';
+import { addSeconds, differenceInSeconds, isDate, maxTime, secondsToMilliseconds } from 'date-fns';
import { create } from 'tar';
import { OCPP16Constants } from './OCPP16Constants';
canProceedChargingProfile,
checkChargingStation,
getConfigurationKey,
+ getConnectorChargingProfiles,
prepareChargingProfileKind,
removeExpiredReservations,
setConfigurationKeyValue,
OCPP16ChargePointStatus,
type OCPP16ChargingProfile,
OCPP16ChargingProfilePurposeType,
- OCPP16ChargingRateUnitType,
type OCPP16ChargingSchedule,
- type OCPP16ChargingSchedulePeriod,
type OCPP16ClearCacheRequest,
type OCPP16DataTransferRequest,
type OCPP16DataTransferResponse,
} from '../../../types';
import {
Constants,
- cloneObject,
convertToDate,
convertToInt,
formatDurationMilliSeconds,
isNotEmptyString,
isNullOrUndefined,
isUndefined,
- isValidTime,
logger,
sleep,
} from '../../../utils';
return OCPP16Constants.OCPP_RESPONSE_REJECTED;
}
if (chargingRateUnit) {
- logger.error(
- `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported`,
+ logger.warn(
+ `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`,
);
- return OCPP16Constants.OCPP_RESPONSE_REJECTED;
}
const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
if (
return OCPP16Constants.OCPP_RESPONSE_REJECTED;
}
const currentDate = new Date();
- const interval: Interval = {
+ const compositeScheduleInterval: Interval = {
start: currentDate,
end: addSeconds(currentDate, duration),
};
- const chargingProfiles: OCPP16ChargingProfile[] = [];
- for (const chargingProfile of cloneObject<OCPP16ChargingProfile[]>(
- (connectorStatus?.chargingProfiles ?? []).concat(
- chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [],
- ),
- ).sort((a, b) => b.stackLevel - a.stackLevel)) {
+ // Get charging profiles sorted by connector id then stack level
+ const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
+ chargingStation,
+ connectorId,
+ );
+ let previousCompositeSchedule: OCPP16ChargingSchedule | undefined;
+ let compositeSchedule: OCPP16ChargingSchedule | undefined;
+ for (const chargingProfile of chargingProfiles) {
if (
- connectorStatus?.transactionStarted &&
- isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule)
+ isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
+ connectorStatus?.transactionStarted
) {
logger.debug(
`${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
// OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart;
}
- if (!isDate(chargingProfile.chargingSchedule?.startSchedule)) {
+ if (
+ !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
+ !isDate(chargingProfile.chargingSchedule?.startSchedule)
+ ) {
logger.warn(
`${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
chargingProfile.chargingProfileId
- } startSchedule property is not a Date object. Trying to convert it to a Date object`,
+ } startSchedule property is not a Date instance. Trying to convert it to a Date instance`,
);
chargingProfile.chargingSchedule.startSchedule = convertToDate(
chargingProfile.chargingSchedule?.startSchedule,
)!;
}
+ if (
+ !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
+ isNullOrUndefined(chargingProfile.chargingSchedule?.duration)
+ ) {
+ logger.debug(
+ `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: 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
+ chargingProfile.chargingSchedule.duration = differenceInSeconds(
+ maxTime,
+ chargingProfile.chargingSchedule.startSchedule!,
+ );
+ }
if (
!prepareChargingProfileKind(
connectorStatus,
chargingProfile,
- interval.start as Date,
+ compositeScheduleInterval.start as Date,
chargingStation.logPrefix(),
)
) {
if (
!canProceedChargingProfile(
chargingProfile,
- interval.start as Date,
+ compositeScheduleInterval.start as Date,
chargingStation.logPrefix(),
)
) {
continue;
}
- // Add active charging profiles into chargingProfiles array
- if (
- isValidTime(chargingProfile.chargingSchedule?.startSchedule) &&
- isWithinInterval(chargingProfile.chargingSchedule.startSchedule!, interval)
- ) {
- chargingProfiles.push(chargingProfile);
- }
- }
- const compositeScheduleStart: Date = min(
- chargingProfiles.map(
- (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime,
- ),
- );
- const compositeScheduleDuration: number = Math.max(
- ...chargingProfiles.map(
- (chargingProfile) => chargingProfile.chargingSchedule.duration ?? -Infinity,
- ),
- );
- // FIXME: remove overlapping charging schedule periods
- const compositeSchedulePeriods: OCPP16ChargingSchedulePeriod[] = chargingProfiles
- .map((chargingProfile) => chargingProfile.chargingSchedule.chargingSchedulePeriod)
- .reduce(
- (accumulator, value) =>
- accumulator.concat(value).sort((a, b) => a.startPeriod - b.startPeriod),
- [],
+ compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
+ previousCompositeSchedule,
+ chargingProfile.chargingSchedule,
+ compositeScheduleInterval,
);
- const compositeSchedule: OCPP16ChargingSchedule = {
- startSchedule: compositeScheduleStart,
- duration: compositeScheduleDuration,
- chargingRateUnit: chargingProfiles.every(
- (chargingProfile) =>
- chargingProfile.chargingSchedule.chargingRateUnit === OCPP16ChargingRateUnitType.AMPERE,
- )
- ? OCPP16ChargingRateUnitType.AMPERE
- : chargingProfiles.every(
- (chargingProfile) =>
- chargingProfile.chargingSchedule.chargingRateUnit === OCPP16ChargingRateUnitType.WATT,
- )
- ? OCPP16ChargingRateUnitType.WATT
- : OCPP16ChargingRateUnitType.AMPERE,
- chargingSchedulePeriod: compositeSchedulePeriods,
- minChargeRate: Math.min(
- ...chargingProfiles.map(
- (chargingProfile) => chargingProfile.chargingSchedule.minChargeRate ?? Infinity,
- ),
- ),
- };
- return {
- status: GenericStatus.Accepted,
- scheduleStart: compositeSchedule.startSchedule!,
- connectorId,
- chargingSchedule: compositeSchedule,
- };
+ previousCompositeSchedule = compositeSchedule;
+ }
+ if (compositeSchedule) {
+ return {
+ status: GenericStatus.Accepted,
+ scheduleStart: compositeSchedule.startSchedule!,
+ connectorId,
+ chargingSchedule: compositeSchedule,
+ };
+ }
+ return OCPP16Constants.OCPP_RESPONSE_REJECTED;
}
private handleRequestClearChargingProfile(