From acfa5fd10b8489f5d85654e654356e65bfd1e0d0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 2 Aug 2023 01:35:51 +0200 Subject: [PATCH] fix: avoid overlapping schedule periods in get composite schedule MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 132 ++++++++++++++---- 1 file changed, 106 insertions(+), 26 deletions(-) diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 6912f765..1941b665 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -8,6 +8,7 @@ import type { JSONSchemaType } from 'ajv'; import { Client, type FTPResponse } from 'basic-ftp'; import { addSeconds, + differenceInSeconds, isAfter, isBefore, isDate, @@ -754,31 +755,107 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { // Add active charging profiles into chargingProfiles array if ( isValidTime(storedChargingProfile.chargingSchedule?.startSchedule) && - isWithinInterval(storedChargingProfile.chargingSchedule.startSchedule!, interval) && - (isEmptyArray(chargingProfiles) || - (isNotEmptyArray(chargingProfiles) && - (isBefore( + isWithinInterval(storedChargingProfile.chargingSchedule.startSchedule!, interval) + ) { + if (isEmptyArray(chargingProfiles)) { + if ( + isAfter( + addSeconds( + storedChargingProfile.chargingSchedule.startSchedule!, + storedChargingProfile.chargingSchedule.duration!, + ), + interval.end, + ) + ) { + storedChargingProfile.chargingSchedule.chargingSchedulePeriod = + storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter( + (schedulePeriod) => + isWithinInterval( + addSeconds( + storedChargingProfile.chargingSchedule.startSchedule!, + schedulePeriod.startPeriod, + )!, + interval, + ), + ); + storedChargingProfile.chargingSchedule.duration = differenceInSeconds( + interval.end, storedChargingProfile.chargingSchedule.startSchedule!, - min( - chargingProfiles.map( - (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime, - ), + ); + } + chargingProfiles.push(storedChargingProfile); + } else if (isNotEmptyArray(chargingProfiles)) { + const chargingProfilesInterval: Interval = { + start: min( + chargingProfiles.map( + (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime, ), - ) || - isAfter( + ), + end: max( + chargingProfiles.map( + (chargingProfile) => + addSeconds( + chargingProfile.chargingSchedule.startSchedule!, + chargingProfile.chargingSchedule.duration!, + ) ?? minTime, + ), + ), + }; + let addChargingProfile = false; + if ( + isBefore(interval.start, chargingProfilesInterval.start) && + isBefore( + storedChargingProfile.chargingSchedule.startSchedule!, + chargingProfilesInterval.start, + ) + ) { + // Remove charging schedule periods that are after the start of the active profiles interval + storedChargingProfile.chargingSchedule.chargingSchedulePeriod = + storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter( + (schedulePeriod) => + isWithinInterval( + addSeconds( + storedChargingProfile.chargingSchedule.startSchedule!, + schedulePeriod.startPeriod, + ), + { + start: interval.start, + end: chargingProfilesInterval.start, + }, + ), + ); + addChargingProfile = true; + } + if ( + isBefore(chargingProfilesInterval.end, interval.end) && + isAfter( + addSeconds( storedChargingProfile.chargingSchedule.startSchedule!, - max( - chargingProfiles.map( - (chargingProfile) => - addSeconds( - chargingProfile.chargingSchedule.startSchedule!, - chargingProfile.chargingSchedule.duration!, - ) ?? minTime, + storedChargingProfile.chargingSchedule.duration!, + ), + chargingProfilesInterval.end, + ) + ) { + // Remove charging schedule periods that are before the end of the active profiles interval + // FIXME: can lead to a gap in the charging schedule: chargingProfilesInterval.end -> first matching schedulePeriod.startPeriod + storedChargingProfile.chargingSchedule.chargingSchedulePeriod = + storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter( + (schedulePeriod) => + isWithinInterval( + addSeconds( + storedChargingProfile.chargingSchedule.startSchedule!, + schedulePeriod.startPeriod, + ), + { + start: chargingProfilesInterval.end, + end: interval.end, + }, ), - ), - )))) - ) { - chargingProfiles.push(storedChargingProfile); + ); + addChargingProfile = true; + } + addChargingProfile && chargingProfiles.push(storedChargingProfile); + } } } const compositeScheduleStart: Date = min( @@ -787,11 +864,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ), ); const compositeScheduleDuration: number = Math.max( - ...chargingProfiles.map( - (chargingProfile) => chargingProfile.chargingSchedule.duration ?? -Infinity, + ...chargingProfiles.map((chargingProfile) => + isNaN(chargingProfile.chargingSchedule.duration!) + ? -Infinity + : chargingProfile.chargingSchedule.duration!, ), ); - // FIXME: remove overlapping charging schedule periods const compositeSchedulePeriods: OCPP16ChargingSchedulePeriod[] = chargingProfiles .map((chargingProfile) => chargingProfile.chargingSchedule.chargingSchedulePeriod) .reduce( @@ -815,8 +893,10 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { : OCPP16ChargingRateUnitType.AMPERE, chargingSchedulePeriod: compositeSchedulePeriods, minChargeRate: Math.min( - ...chargingProfiles.map( - (chargingProfile) => chargingProfile.chargingSchedule.minChargeRate ?? Infinity, + ...chargingProfiles.map((chargingProfile) => + isNaN(chargingProfile.chargingSchedule.minChargeRate!) + ? Infinity + : chargingProfile.chargingSchedule.minChargeRate!, ), ), }; -- 2.34.1