+ return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
+ }
+
+ private handleRequestGetCompositeSchedule(
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16GetCompositeScheduleRequest,
+ ): OCPP16GetCompositeScheduleResponse {
+ if (
+ OCPP16ServiceUtils.checkFeatureProfile(
+ chargingStation,
+ OCPP16SupportedFeatureProfiles.SmartCharging,
+ OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
+ ) === false
+ ) {
+ return OCPP16Constants.OCPP_RESPONSE_REJECTED;
+ }
+ const { connectorId, duration, chargingRateUnit } = commandPayload;
+ if (chargingStation.hasConnector(connectorId) === false) {
+ logger.error(
+ `${chargingStation.logPrefix()} Trying to get composite schedule to a
+ non existing connector id ${connectorId}`,
+ );
+ return OCPP16Constants.OCPP_RESPONSE_REJECTED;
+ }
+ if (connectorId === 0) {
+ logger.error(
+ `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`,
+ );
+ return OCPP16Constants.OCPP_RESPONSE_REJECTED;
+ }
+ if (chargingRateUnit) {
+ logger.error(
+ `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported`,
+ );
+ return OCPP16Constants.OCPP_RESPONSE_REJECTED;
+ }
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
+ if (
+ isEmptyArray(
+ connectorStatus?.chargingProfiles &&
+ isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles),
+ )
+ ) {
+ return OCPP16Constants.OCPP_RESPONSE_REJECTED;
+ }
+ const currentDate = new Date();
+ const interval: 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)) {
+ if (
+ connectorStatus?.transactionStarted &&
+ isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule)
+ ) {
+ 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 (!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`,
+ );
+ chargingProfile.chargingSchedule.startSchedule = convertToDate(
+ chargingProfile.chargingSchedule?.startSchedule,
+ )!;
+ }
+ if (
+ !prepareChargingProfileKind(
+ connectorStatus,
+ chargingProfile,
+ interval.start as Date,
+ chargingStation.logPrefix(),
+ )
+ ) {
+ continue;
+ }
+ if (
+ !canProceedChargingProfile(
+ chargingProfile,
+ interval.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 compositeSchedule: OCPP16ChargingSchedule = {
+ startSchedule: min(
+ chargingProfiles.map(
+ (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime,
+ ),
+ ),
+ duration: Math.max(
+ ...chargingProfiles.map(
+ (chargingProfile) => chargingProfile.chargingSchedule.duration ?? -Infinity,
+ ),
+ ),
+ chargingRateUnit: chargingProfiles.every(
+ (chargingProfile) =>
+ chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.AMPERE,
+ )
+ ? ChargingRateUnitType.AMPERE
+ : chargingProfiles.every(
+ (chargingProfile) =>
+ chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT,
+ )
+ ? ChargingRateUnitType.WATT
+ : ChargingRateUnitType.AMPERE,
+ // FIXME: remove overlapping charging schedule periods
+ chargingSchedulePeriod: chargingProfiles
+ .map((chargingProfile) => chargingProfile.chargingSchedule.chargingSchedulePeriod)
+ .reduce(
+ (accumulator, value) =>
+ accumulator.concat(value).sort((a, b) => a.startPeriod - b.startPeriod),
+ [],
+ ),
+ minChargeRate: Math.min(
+ ...chargingProfiles.map(
+ (chargingProfile) => chargingProfile.chargingSchedule.minChargeRate ?? Infinity,
+ ),
+ ),
+ };
+ return {
+ status: GenericStatus.Accepted,
+ scheduleStart: compositeSchedule.startSchedule!,
+ connectorId,
+ chargingSchedule: compositeSchedule,
+ };