X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2F1.6%2FOCPP16ServiceUtils.ts;h=734bfcd26f872069c4a8995ab56ff4656e6a370f;hb=c6dcc3311af6547a0d2d618b8c27a84424c27b4b;hp=b510051e2085a3ce0d006a1765f05be7603a3318;hpb=ef9e3b334b977bd79231adb7e59c3875313d32dc;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts index b510051e..734bfcd2 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts @@ -45,7 +45,6 @@ import { type OCPP16SupportedFeatureProfiles, OCPPVersion, type SampledValueTemplate, - Voltage, } from '../../../types'; import { ACElectricUtils, @@ -57,6 +56,7 @@ import { getRandomFloatRounded, getRandomInteger, isNotEmptyArray, + isNotEmptyString, isNullOrUndefined, isUndefined, logger, @@ -102,7 +102,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { if (socSampledValueTemplate) { const socMaximumValue = 100; const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0; - const socSampledValueTemplateValue = socSampledValueTemplate.value + const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value) ? getRandomFloatFluctuatedRounded( parseInt(socSampledValueTemplate.value), socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -123,7 +123,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${ meterValue.sampledValue[sampledValuesIndex].value - }/${socMaximumValue}}`, + }/${socMaximumValue}`, ); } } @@ -134,9 +134,9 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16MeterValueMeasurand.VOLTAGE, ); if (voltageSampledValueTemplate) { - const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value + const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value) ? parseInt(voltageSampledValueTemplate.value) - : chargingStation.getVoltageOut(); + : chargingStation.stationInfo.voltageOut!; const fluctuationPercent = voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; const voltageMeasurandValue = getRandomFloatFluctuatedRounded( @@ -145,7 +145,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { ); if ( chargingStation.getNumberOfPhases() !== 3 || - (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues()) + (chargingStation.getNumberOfPhases() === 3 && + chargingStation.stationInfo?.mainVoltageMeterValues) ) { meterValue.sampledValue.push( OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue), @@ -166,10 +167,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { ); let voltagePhaseLineToNeutralMeasurandValue: number | undefined; if (voltagePhaseLineToNeutralSampledValueTemplate) { - const voltagePhaseLineToNeutralSampledValueTemplateValue = - voltagePhaseLineToNeutralSampledValueTemplate.value - ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) - : chargingStation.getVoltageOut(); + const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString( + voltagePhaseLineToNeutralSampledValueTemplate.value, + ) + ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) + : chargingStation.stationInfo.voltageOut!; const fluctuationPhaseToNeutralPercent = voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; @@ -186,12 +188,17 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { phaseLineToNeutralValue as OCPP16MeterValuePhase, ), ); - if (chargingStation.getPhaseLineToLineVoltageMeterValues()) { + if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) { const phaseLineToLineValue = `L${phase}-L${ (phase + 1) % chargingStation.getNumberOfPhases() !== 0 ? (phase + 1) % chargingStation.getNumberOfPhases() : chargingStation.getNumberOfPhases() }`; + const voltagePhaseLineToLineValueRounded = roundTo( + Math.sqrt(chargingStation.getNumberOfPhases()) * + chargingStation.stationInfo.voltageOut!, + 2, + ); const voltagePhaseLineToLineSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( chargingStation, @@ -201,10 +208,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { ); let voltagePhaseLineToLineMeasurandValue: number | undefined; if (voltagePhaseLineToLineSampledValueTemplate) { - const voltagePhaseLineToLineSampledValueTemplateValue = - voltagePhaseLineToLineSampledValueTemplate.value - ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) - : Voltage.VOLTAGE_400; + const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString( + voltagePhaseLineToLineSampledValueTemplate.value, + ) + ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) + : voltagePhaseLineToLineValueRounded; const fluctuationPhaseLineToLinePercent = voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; @@ -214,7 +222,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { ); } const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( - Voltage.VOLTAGE_400, + voltagePhaseLineToLineValueRounded, fluctuationPercent, ); meterValue.sampledValue.push( @@ -265,7 +273,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { const errMsg = `MeterValues measurand ${ powerSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ + }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ chargingStation.templateFile }, cannot calculate ${ powerSampledValueTemplate.measurand ?? @@ -279,11 +287,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { const connectorMaximumPowerPerPhase = Math.round( connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(), ); - const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue!) ?? 0; + const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue ?? 0); const connectorMinimumPowerPerPhase = Math.round( connectorMinimumPower / chargingStation.getNumberOfPhases(), ); - switch (chargingStation.getCurrentOutType()) { + switch (chargingStation.stationInfo?.currentOutType) { case CurrentType.AC: if (chargingStation.getNumberOfPhases() === 3) { const defaultFluctuatedPowerPerPhase = @@ -292,7 +300,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( powerSampledValueTemplate.value, connectorMaximumPower / unitDivider, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumPower / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPower / unitDivider, + }, ) / chargingStation.getNumberOfPhases(), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -303,7 +316,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( powerPerPhaseSampledValueTemplates.L1.value, connectorMaximumPowerPerPhase / unitDivider, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumPowerPerPhase / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPowerPerPhase / unitDivider, + }, ), powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -314,7 +332,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( powerPerPhaseSampledValueTemplates.L2.value, connectorMaximumPowerPerPhase / unitDivider, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumPowerPerPhase / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPowerPerPhase / unitDivider, + }, ), powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -325,7 +348,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( powerPerPhaseSampledValueTemplates.L3.value, connectorMaximumPowerPerPhase / unitDivider, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumPowerPerPhase / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPowerPerPhase / unitDivider, + }, ), powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -352,12 +380,17 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { connectorMinimumPowerPerPhase / unitDivider, ); } else { - powerMeasurandValues.L1 = powerSampledValueTemplate.value + powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value) ? getRandomFloatFluctuatedRounded( OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( powerSampledValueTemplate.value, connectorMaximumPower / unitDivider, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumPower / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPower / unitDivider, + }, ), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -375,12 +408,17 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { ); break; case CurrentType.DC: - powerMeasurandValues.allPhases = powerSampledValueTemplate.value + powerMeasurandValues.allPhases = isNotEmptyString(powerSampledValueTemplate.value) ? getRandomFloatFluctuatedRounded( OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( powerSampledValueTemplate.value, connectorMaximumPower / unitDivider, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumPower / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPower / unitDivider, + }, ), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -501,7 +539,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { const errMsg = `MeterValues measurand ${ currentSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ + }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ chargingStation.templateFile }, cannot calculate ${ currentSampledValueTemplate.measurand ?? @@ -512,12 +550,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { chargingStation.getConnectorMaximumAvailablePower(connectorId); const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0; let connectorMaximumAmperage: number; - switch (chargingStation.getCurrentOutType()) { + switch (chargingStation.stationInfo?.currentOutType) { case CurrentType.AC: connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( chargingStation.getNumberOfPhases(), connectorMaximumAvailablePower, - chargingStation.getVoltageOut(), + chargingStation.stationInfo.voltageOut!, ); if (chargingStation.getNumberOfPhases() === 3) { const defaultFluctuatedAmperagePerPhase = @@ -526,7 +564,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( currentSampledValueTemplate.value, connectorMaximumAmperage, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, ), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -537,7 +580,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( currentPerPhaseSampledValueTemplates.L1.value, connectorMaximumAmperage, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, ), currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -548,7 +596,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( currentPerPhaseSampledValueTemplates.L2.value, connectorMaximumAmperage, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, ), currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -559,7 +612,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( currentPerPhaseSampledValueTemplates.L3.value, connectorMaximumAmperage, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, ), currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -577,12 +635,17 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { (defaultFluctuatedAmperagePerPhase as number) ?? getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); } else { - currentMeasurandValues.L1 = currentSampledValueTemplate.value + currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value) ? getRandomFloatFluctuatedRounded( OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( currentSampledValueTemplate.value, connectorMaximumAmperage, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, ), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -600,14 +663,19 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { case CurrentType.DC: connectorMaximumAmperage = DCElectricUtils.amperage( connectorMaximumAvailablePower, - chargingStation.getVoltageOut(), + chargingStation.stationInfo.voltageOut!, ); - currentMeasurandValues.allPhases = currentSampledValueTemplate.value + currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value) ? getRandomFloatFluctuatedRounded( OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( currentSampledValueTemplate.value, connectorMaximumAmperage, - { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, ), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, @@ -696,20 +764,25 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { (connectorMaximumAvailablePower * interval) / (3600 * 1000), 2, ); - const energyValueRounded = energySampledValueTemplate.value - ? // Cumulate the fluctuated value around the static one - getRandomFloatFluctuatedRounded( + const connectorMinimumEnergyRounded = roundTo( + energySampledValueTemplate.minimumValue ?? 0, + 2, + ); + const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value) + ? getRandomFloatFluctuatedRounded( OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( energySampledValueTemplate.value, connectorMaximumEnergyRounded, + connectorMinimumEnergyRounded, { - limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(), + limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues, unitMultiplier: unitDivider, + fallbackValue: connectorMinimumEnergyRounded, }, ), energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, ) - : getRandomFloatRounded(connectorMaximumEnergyRounded); + : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded); // Persist previous value on connector if (connector) { if ( @@ -736,12 +809,16 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { ), ); const sampledValuesIndex = meterValue.sampledValue.length - 1; - if (energyValueRounded > connectorMaximumEnergyRounded || debug) { + if ( + energyValueRounded > connectorMaximumEnergyRounded || + energyValueRounded < connectorMinimumEnergyRounded || + debug + ) { logger.error( `${chargingStation.logPrefix()} MeterValues measurand ${ meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`, + }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`, ); } } @@ -933,49 +1010,195 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { }; public static composeChargingSchedules = ( - chargingSchedule1: OCPP16ChargingSchedule | undefined, - chargingSchedule2: OCPP16ChargingSchedule | undefined, - targetInterval: Interval, + chargingScheduleHigher: OCPP16ChargingSchedule | undefined, + chargingScheduleLower: OCPP16ChargingSchedule | undefined, + compositeInterval: Interval, ): OCPP16ChargingSchedule | undefined => { - if (!chargingSchedule1 && !chargingSchedule2) { + if (!chargingScheduleHigher && !chargingScheduleLower) { return undefined; } - if (chargingSchedule1 && !chargingSchedule2) { - return OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1, targetInterval); + if (chargingScheduleHigher && !chargingScheduleLower) { + return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval); } - if (!chargingSchedule1 && chargingSchedule2) { - return OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2, targetInterval); + if (!chargingScheduleHigher && chargingScheduleLower) { + return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval); } - const compositeChargingSchedule1: OCPP16ChargingSchedule | undefined = - OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1!, targetInterval); - const compositeChargingSchedule2: OCPP16ChargingSchedule | undefined = - OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2!, targetInterval); - const compositeChargingScheduleInterval1: Interval = { - start: compositeChargingSchedule1!.startSchedule!, + const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined = + OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval); + const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined = + OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval); + const compositeChargingScheduleHigherInterval: Interval = { + start: compositeChargingScheduleHigher!.startSchedule!, end: addSeconds( - compositeChargingSchedule1!.startSchedule!, - compositeChargingSchedule1!.duration!, + compositeChargingScheduleHigher!.startSchedule!, + compositeChargingScheduleHigher!.duration!, ), }; - const compositeChargingScheduleInterval2: Interval = { - start: compositeChargingSchedule2!.startSchedule!, + const compositeChargingScheduleLowerInterval: Interval = { + start: compositeChargingScheduleLower!.startSchedule!, end: addSeconds( - compositeChargingSchedule2!.startSchedule!, - compositeChargingSchedule2!.duration!, + compositeChargingScheduleLower!.startSchedule!, + compositeChargingScheduleLower!.duration!, ), }; + const higherFirst = isBefore( + compositeChargingScheduleHigherInterval.start, + compositeChargingScheduleLowerInterval.start, + ); if ( !areIntervalsOverlapping( - compositeChargingScheduleInterval1, - compositeChargingScheduleInterval2, + compositeChargingScheduleHigherInterval, + compositeChargingScheduleLowerInterval, ) ) { return { - ...OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1!, targetInterval)!, - ...OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2!, targetInterval)!, + ...compositeChargingScheduleLower, + ...compositeChargingScheduleHigher!, + startSchedule: higherFirst + ? (compositeChargingScheduleHigherInterval.start as Date) + : (compositeChargingScheduleLowerInterval.start as Date), + duration: higherFirst + ? differenceInSeconds( + compositeChargingScheduleLowerInterval.end, + compositeChargingScheduleHigherInterval.start, + ) + : differenceInSeconds( + compositeChargingScheduleHigherInterval.end, + compositeChargingScheduleLowerInterval.start, + ), + chargingSchedulePeriod: [ + ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { + return { + ...schedulePeriod, + startPeriod: higherFirst + ? 0 + : schedulePeriod.startPeriod + + differenceInSeconds( + compositeChargingScheduleHigherInterval.start, + compositeChargingScheduleLowerInterval.start, + ), + }; + }), + ...compositeChargingScheduleLower!.chargingSchedulePeriod.map((schedulePeriod) => { + return { + ...schedulePeriod, + startPeriod: higherFirst + ? schedulePeriod.startPeriod + + differenceInSeconds( + compositeChargingScheduleLowerInterval.start, + compositeChargingScheduleHigherInterval.start, + ) + : 0, + }; + }), + ].sort((a, b) => a.startPeriod - b.startPeriod), }; } - // FIXME: Handle overlapping intervals + return { + ...compositeChargingScheduleLower, + ...compositeChargingScheduleHigher!, + startSchedule: higherFirst + ? (compositeChargingScheduleHigherInterval.start as Date) + : (compositeChargingScheduleLowerInterval.start as Date), + duration: higherFirst + ? differenceInSeconds( + compositeChargingScheduleLowerInterval.end, + compositeChargingScheduleHigherInterval.start, + ) + : differenceInSeconds( + compositeChargingScheduleHigherInterval.end, + compositeChargingScheduleLowerInterval.start, + ), + chargingSchedulePeriod: [ + ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { + return { + ...schedulePeriod, + startPeriod: higherFirst + ? 0 + : schedulePeriod.startPeriod + + differenceInSeconds( + compositeChargingScheduleHigherInterval.start, + compositeChargingScheduleLowerInterval.start, + ), + }; + }), + ...compositeChargingScheduleLower!.chargingSchedulePeriod + .filter((schedulePeriod, index) => { + if ( + higherFirst && + isWithinInterval( + addSeconds( + compositeChargingScheduleLowerInterval.start, + schedulePeriod.startPeriod, + ), + { + start: compositeChargingScheduleLowerInterval.start, + end: compositeChargingScheduleHigherInterval.end, + }, + ) + ) { + return false; + } + if ( + higherFirst && + index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 && + !isWithinInterval( + addSeconds( + compositeChargingScheduleLowerInterval.start, + schedulePeriod.startPeriod, + ), + { + start: compositeChargingScheduleLowerInterval.start, + end: compositeChargingScheduleHigherInterval.end, + }, + ) && + isWithinInterval( + addSeconds( + compositeChargingScheduleLowerInterval.start, + compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod, + ), + { + start: compositeChargingScheduleLowerInterval.start, + end: compositeChargingScheduleHigherInterval.end, + }, + ) + ) { + return false; + } + if ( + !higherFirst && + isWithinInterval( + addSeconds( + compositeChargingScheduleLowerInterval.start, + schedulePeriod.startPeriod, + ), + { + start: compositeChargingScheduleHigherInterval.start, + end: compositeChargingScheduleLowerInterval.end, + }, + ) + ) { + return false; + } + return true; + }) + .map((schedulePeriod, index) => { + if (index === 0 && schedulePeriod.startPeriod !== 0) { + schedulePeriod.startPeriod = 0; + } + return { + ...schedulePeriod, + startPeriod: higherFirst + ? schedulePeriod.startPeriod + + differenceInSeconds( + compositeChargingScheduleLowerInterval.start, + compositeChargingScheduleHigherInterval.start, + ) + : 0, + }; + }), + ].sort((a, b) => a.startPeriod - b.startPeriod), + }; }; public static hasReservation = ( @@ -1021,25 +1244,28 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { private static composeChargingSchedule = ( chargingSchedule: OCPP16ChargingSchedule, - targetInterval: Interval, + compositeInterval: Interval, ): OCPP16ChargingSchedule | undefined => { const chargingScheduleInterval: Interval = { start: chargingSchedule.startSchedule!, end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), }; - if (areIntervalsOverlapping(chargingScheduleInterval, targetInterval)) { + if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) { chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod); - if (isBefore(chargingScheduleInterval.start, targetInterval.start)) { + if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) { return { ...chargingSchedule, - startSchedule: targetInterval.start as Date, - duration: differenceInSeconds(chargingScheduleInterval.end, targetInterval.start as Date), - chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter( - (schedulePeriod, index) => { + startSchedule: compositeInterval.start as Date, + duration: differenceInSeconds( + chargingScheduleInterval.end, + compositeInterval.start as Date, + ), + chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod + .filter((schedulePeriod, index) => { if ( isWithinInterval( addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!, - targetInterval, + compositeInterval, ) ) { return true; @@ -1048,32 +1274,39 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { index < chargingSchedule.chargingSchedulePeriod.length - 1 && !isWithinInterval( addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod), - targetInterval, + compositeInterval, ) && isWithinInterval( addSeconds( chargingScheduleInterval.start, chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod, ), - targetInterval, + compositeInterval, ) ) { - schedulePeriod.startPeriod = 0; return true; } return false; - }, - ), + }) + .map((schedulePeriod, index) => { + if (index === 0 && schedulePeriod.startPeriod !== 0) { + schedulePeriod.startPeriod = 0; + } + return schedulePeriod; + }), }; } - if (isAfter(chargingScheduleInterval.end, targetInterval.end)) { + if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) { return { ...chargingSchedule, - duration: differenceInSeconds(targetInterval.end as Date, chargingScheduleInterval.start), + duration: differenceInSeconds( + compositeInterval.end as Date, + chargingScheduleInterval.start, + ), chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter((schedulePeriod) => isWithinInterval( addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!, - targetInterval, + compositeInterval, ), ), }; @@ -1088,12 +1321,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { context?: MeterValueContext, phase?: OCPP16MeterValuePhase, ): OCPP16SampledValue { - const sampledValueValue = value ?? sampledValueTemplate?.value ?? null; - const sampledValueContext = context ?? sampledValueTemplate?.context ?? null; + const sampledValueValue = value ?? sampledValueTemplate?.value; + const sampledValueContext = context ?? sampledValueTemplate?.context; const sampledValueLocation = sampledValueTemplate?.location ?? OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!); - const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null; + const sampledValuePhase = phase ?? sampledValueTemplate?.phase; return { ...(!isNullOrUndefined(sampledValueTemplate.unit) && { unit: sampledValueTemplate.unit,