import type { JSONSchemaType } from 'ajv';
import { Client, type FTPResponse } from 'basic-ftp';
-import { addSeconds, isWithinInterval, max, secondsToMilliseconds } from 'date-fns';
+import { addSeconds, differenceInSeconds, isDate, maxTime, secondsToMilliseconds } from 'date-fns';
import { create } from 'tar';
import { OCPP16Constants } from './OCPP16Constants';
import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
import {
type ChargingStation,
+ canProceedChargingProfile,
checkChargingStation,
getConfigurationKey,
+ getConnectorChargingProfiles,
+ prepareChargingProfileKind,
removeExpiredReservations,
setConfigurationKeyValue,
} from '../../../charging-station';
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;
}
- if (isEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
+ if (
+ isEmptyArray(
+ connectorStatus?.chargingProfiles &&
+ isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles),
+ )
+ ) {
return OCPP16Constants.OCPP_RESPONSE_REJECTED;
}
- const startDate = new Date();
- const endDate = addSeconds(startDate, duration);
+ const currentDate = new Date();
+ const compositeScheduleInterval: Interval = {
+ start: currentDate,
+ end: addSeconds(currentDate, duration),
+ };
+ // 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 chargingStation.getConnectorStatus(connectorId)!
- .chargingProfiles!) {
+ for (const chargingProfile of chargingProfiles) {
if (
- compositeSchedule?.chargingRateUnit &&
- compositeSchedule.chargingRateUnit !== chargingProfile.chargingSchedule.chargingRateUnit
+ isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
+ connectorStatus?.transactionStarted
) {
- logger.error(
- `${chargingStation.logPrefix()} Building composite schedule with different charging rate units is not yet supported, skipping charging profile id ${
+ logger.debug(
+ `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: 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
+ chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart;
+ }
+ 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 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,
+ compositeScheduleInterval.start as Date,
+ chargingStation.logPrefix(),
+ )
+ ) {
continue;
}
if (
- isWithinInterval(chargingProfile.chargingSchedule.startSchedule!, {
- start: startDate,
- end: endDate,
- }) &&
- isWithinInterval(
- addSeconds(
- chargingProfile.chargingSchedule.startSchedule!,
- chargingProfile.chargingSchedule.duration!,
- ),
- {
- start: startDate,
- end: endDate,
- },
+ !canProceedChargingProfile(
+ chargingProfile,
+ compositeScheduleInterval.start as Date,
+ chargingStation.logPrefix(),
)
) {
- compositeSchedule = {
- startSchedule: max([
- compositeSchedule?.startSchedule ?? startDate,
- chargingProfile.chargingSchedule.startSchedule!,
- ]),
- duration: Math.max(
- compositeSchedule?.duration ?? -Infinity,
- chargingProfile.chargingSchedule.duration!,
- ),
- chargingRateUnit: chargingProfile.chargingSchedule.chargingRateUnit,
- ...(compositeSchedule?.chargingSchedulePeriod === undefined
- ? { chargingSchedulePeriod: [] }
- : {
- chargingSchedulePeriod: compositeSchedule.chargingSchedulePeriod.concat(
- ...chargingProfile.chargingSchedule.chargingSchedulePeriod,
- ),
- }),
- ...(chargingProfile.chargingSchedule.minChargeRate && {
- minChargeRate: Math.min(
- compositeSchedule?.minChargeRate ?? Infinity,
- chargingProfile.chargingSchedule.minChargeRate,
- ),
- }),
- };
+ continue;
}
+ compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
+ previousCompositeSchedule,
+ chargingProfile.chargingSchedule,
+ compositeScheduleInterval,
+ );
+ previousCompositeSchedule = compositeSchedule;
}
- return {
- status: GenericStatus.Accepted,
- scheduleStart: compositeSchedule?.startSchedule,
- connectorId,
- chargingSchedule: compositeSchedule,
- };
+ if (compositeSchedule) {
+ return {
+ status: GenericStatus.Accepted,
+ scheduleStart: compositeSchedule.startSchedule!,
+ connectorId,
+ chargingSchedule: compositeSchedule,
+ };
+ }
+ return OCPP16Constants.OCPP_RESPONSE_REJECTED;
}
private handleRequestClearChargingProfile(