X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2F1.6%2FOCPP16ServiceUtils.ts;h=2a154e7b78e9dd26a6ebb2b300f968c7dba3f712;hb=844e496b3482e49145467af3f74df54811e91cb6;hp=64b49c46d4f2f165ec320d56fcfead811babf048;hpb=71a77ac2f725a07cf391ad34e56dbd0dbb194329;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 64b49c46..2a154e7b 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts @@ -1,10 +1,15 @@ // Partial Copyright Jerome Benoit. 2021. All Rights Reserved. -import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils'; +import OCPPError from '../../../exception/OCPPError'; import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate'; import MeasurandPerPhaseSampledValueTemplates, { SampledValueTemplate, } from '../../../types/MeasurandPerPhaseSampledValueTemplates'; +import MeasurandValues from '../../../types/MeasurandValues'; +import { + OCPP16StandardParametersKey, + OCPP16SupportedFeatureProfiles, +} from '../../../types/ocpp/1.6/Configuration'; import { MeterValueContext, MeterValueLocation, @@ -14,91 +19,34 @@ import { OCPP16MeterValuePhase, OCPP16SampledValue, } from '../../../types/ocpp/1.6/MeterValues'; - -import type ChargingStation from '../../ChargingStation'; -import Constants from '../../../utils/Constants'; +import { + OCPP16IncomingRequestCommand, + OCPP16RequestCommand, +} from '../../../types/ocpp/1.6/Requests'; import { ErrorType } from '../../../types/ocpp/ErrorType'; -import MeasurandValues from '../../../types/MeasurandValues'; -import { OCPP16RequestCommand } from '../../../types/ocpp/1.6/Requests'; -import OCPPError from '../../../exception/OCPPError'; -import Utils from '../../../utils/Utils'; +import Constants from '../../../utils/Constants'; +import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils'; import logger from '../../../utils/Logger'; +import Utils from '../../../utils/Utils'; +import type ChargingStation from '../../ChargingStation'; +import { ChargingStationUtils } from '../../ChargingStationUtils'; +import { OCPPServiceUtils } from '../OCPPServiceUtils'; -export class OCPP16ServiceUtils { - public static checkMeasurandPowerDivider( +export class OCPP16ServiceUtils extends OCPPServiceUtils { + public static checkFeatureProfile( chargingStation: ChargingStation, - measurandType: OCPP16MeterValueMeasurand - ): void { - if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) { - const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ - measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: powerDivider is undefined`; - logger.error(errMsg); - throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); - } else if (chargingStation.stationInfo?.powerDivider <= 0) { - const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ - measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`; - logger.error(errMsg); - throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); - } - } - - public static buildSampledValue( - sampledValueTemplate: SampledValueTemplate, - value: number, - context?: MeterValueContext, - phase?: OCPP16MeterValuePhase - ): OCPP16SampledValue { - const sampledValueValue = value ?? sampledValueTemplate?.value ?? null; - const sampledValueContext = context ?? sampledValueTemplate?.context ?? null; - const sampledValueLocation = - sampledValueTemplate?.location ?? - OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null); - const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null; - return { - ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && { - unit: sampledValueTemplate.unit, - }), - ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), - ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && { - measurand: sampledValueTemplate.measurand, - }), - ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), - ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }), - ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), - }; - } - - public static getMeasurandDefaultUnit( - measurandType: OCPP16MeterValueMeasurand - ): MeterValueUnit | undefined { - switch (measurandType) { - case OCPP16MeterValueMeasurand.CURRENT_EXPORT: - case OCPP16MeterValueMeasurand.CURRENT_IMPORT: - case OCPP16MeterValueMeasurand.CURRENT_OFFERED: - return MeterValueUnit.AMP; - case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: - case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: - return MeterValueUnit.WATT_HOUR; - case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: - case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: - case OCPP16MeterValueMeasurand.POWER_OFFERED: - return MeterValueUnit.WATT; - case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: - return MeterValueUnit.PERCENT; - case OCPP16MeterValueMeasurand.VOLTAGE: - return MeterValueUnit.VOLT; - } - } - - public static getMeasurandDefaultLocation( - measurandType: OCPP16MeterValueMeasurand - ): MeterValueLocation | undefined { - switch (measurandType) { - case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: - return MeterValueLocation.EV; + featureProfile: OCPP16SupportedFeatureProfiles, + command: OCPP16RequestCommand | OCPP16IncomingRequestCommand + ): boolean { + if (!chargingStation.hasFeatureProfile(featureProfile)) { + logger.warn( + `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${ + OCPP16StandardParametersKey.SupportedFeatureProfiles + } in configuration` + ); + return false; } + return true; } public static buildMeterValue( @@ -114,7 +62,8 @@ export class OCPP16ServiceUtils { }; const connector = chargingStation.getConnectorStatus(connectorId); // SoC measurand - const socSampledValueTemplate = chargingStation.getSampledValueTemplate( + const socSampledValueTemplate = ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.STATE_OF_CHARGE ); @@ -141,7 +90,8 @@ export class OCPP16ServiceUtils { } } // Voltage measurand - const voltageSampledValueTemplate = chargingStation.getSampledValueTemplate( + const voltageSampledValueTemplate = ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.VOLTAGE ); @@ -170,7 +120,8 @@ export class OCPP16ServiceUtils { ) { const phaseLineToNeutralValue = `L${phase}-N`; const voltagePhaseLineToNeutralSampledValueTemplate = - chargingStation.getSampledValueTemplate( + ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.VOLTAGE, phaseLineToNeutralValue as OCPP16MeterValuePhase @@ -204,7 +155,8 @@ export class OCPP16ServiceUtils { : chargingStation.getNumberOfPhases() }`; const voltagePhaseLineToLineSampledValueTemplate = - chargingStation.getSampledValueTemplate( + ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.VOLTAGE, phaseLineToLineValue as OCPP16MeterValuePhase @@ -239,24 +191,28 @@ export class OCPP16ServiceUtils { } } // Power.Active.Import measurand - const powerSampledValueTemplate = chargingStation.getSampledValueTemplate( + const powerSampledValueTemplate = ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT ); let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; if (chargingStation.getNumberOfPhases() === 3) { powerPerPhaseSampledValueTemplates = { - L1: chargingStation.getSampledValueTemplate( + L1: ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, OCPP16MeterValuePhase.L1_N ), - L2: chargingStation.getSampledValueTemplate( + L2: ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, OCPP16MeterValuePhase.L2_N ), - L3: chargingStation.getSampledValueTemplate( + L3: ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, OCPP16MeterValuePhase.L3_N @@ -279,13 +235,11 @@ export class OCPP16ServiceUtils { } measurand value`; const powerMeasurandValues = {} as MeasurandValues; const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; - const maximumPower = Math.round( - chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider - ); - const maximumPowerPerPhase = Math.round( - chargingStation.getMaximumConfiguredPower() / - chargingStation.stationInfo.powerDivider / - chargingStation.getNumberOfPhases() + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId); + const connectorMaximumPower = Math.round(connectorMaximumAvailablePower); + const connectorMaximumPowerPerPhase = Math.round( + connectorMaximumAvailablePower / chargingStation.getNumberOfPhases() ); switch (chargingStation.getCurrentOutType()) { case CurrentType.AC: @@ -293,51 +247,71 @@ export class OCPP16ServiceUtils { const defaultFluctuatedPowerPerPhase = powerSampledValueTemplate.value && Utils.getRandomFloatFluctuatedRounded( - parseInt(powerSampledValueTemplate.value) / chargingStation.getNumberOfPhases(), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + powerSampledValueTemplate.value, + connectorMaximumPower / unitDivider, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ) / chargingStation.getNumberOfPhases(), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ); const phase1FluctuatedValue = powerPerPhaseSampledValueTemplates?.L1?.value && Utils.getRandomFloatFluctuatedRounded( - parseInt(powerPerPhaseSampledValueTemplates.L1.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + powerPerPhaseSampledValueTemplates.L1.value, + connectorMaximumPowerPerPhase / unitDivider, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ); const phase2FluctuatedValue = powerPerPhaseSampledValueTemplates?.L2?.value && Utils.getRandomFloatFluctuatedRounded( - parseInt(powerPerPhaseSampledValueTemplates.L2.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + powerPerPhaseSampledValueTemplates.L2.value, + connectorMaximumPowerPerPhase / unitDivider, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ); const phase3FluctuatedValue = powerPerPhaseSampledValueTemplates?.L3?.value && Utils.getRandomFloatFluctuatedRounded( - parseInt(powerPerPhaseSampledValueTemplates.L3.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + powerPerPhaseSampledValueTemplates.L3.value, + connectorMaximumPowerPerPhase / unitDivider, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ); powerMeasurandValues.L1 = phase1FluctuatedValue ?? defaultFluctuatedPowerPerPhase ?? - Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider); + Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); powerMeasurandValues.L2 = phase2FluctuatedValue ?? defaultFluctuatedPowerPerPhase ?? - Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider); + Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); powerMeasurandValues.L3 = phase3FluctuatedValue ?? defaultFluctuatedPowerPerPhase ?? - Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider); + Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); } else { powerMeasurandValues.L1 = powerSampledValueTemplate.value ? Utils.getRandomFloatFluctuatedRounded( - parseInt(powerSampledValueTemplate.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + powerSampledValueTemplate.value, + connectorMaximumPower / unitDivider, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumPower / unitDivider); + : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider); powerMeasurandValues.L2 = 0; powerMeasurandValues.L3 = 0; } @@ -349,11 +323,15 @@ export class OCPP16ServiceUtils { case CurrentType.DC: powerMeasurandValues.allPhases = powerSampledValueTemplate.value ? Utils.getRandomFloatFluctuatedRounded( - parseInt(powerSampledValueTemplate.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + powerSampledValueTemplate.value, + connectorMaximumPower / unitDivider, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumPower / unitDivider); + : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider); break; default: logger.error(errMsg); @@ -366,10 +344,10 @@ export class OCPP16ServiceUtils { ) ); const sampledValuesIndex = meterValue.sampledValue.length - 1; - const maximumPowerRounded = Utils.roundTo(maximumPower / unitDivider, 2); + const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2); if ( Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > - maximumPowerRounded || + connectorMaximumPowerRounded || debug ) { logger.error( @@ -378,7 +356,7 @@ export class OCPP16ServiceUtils { OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ meterValue.sampledValue[sampledValuesIndex].value - }/${maximumPowerRounded}` + }/${connectorMaximumPowerRounded}` ); } for ( @@ -397,10 +375,13 @@ export class OCPP16ServiceUtils { ) ); const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; - const maximumPowerPerPhaseRounded = Utils.roundTo(maximumPowerPerPhase / unitDivider, 2); + const connectorMaximumPowerPerPhaseRounded = Utils.roundTo( + connectorMaximumPowerPerPhase / unitDivider, + 2 + ); if ( Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > - maximumPowerPerPhaseRounded || + connectorMaximumPowerPerPhaseRounded || debug ) { logger.error( @@ -411,30 +392,34 @@ export class OCPP16ServiceUtils { meterValue.sampledValue[sampledValuesPerPhaseIndex].phase }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ meterValue.sampledValue[sampledValuesPerPhaseIndex].value - }/${maximumPowerPerPhaseRounded}` + }/${connectorMaximumPowerPerPhaseRounded}` ); } } } // Current.Import measurand - const currentSampledValueTemplate = chargingStation.getSampledValueTemplate( + const currentSampledValueTemplate = ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.CURRENT_IMPORT ); let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; if (chargingStation.getNumberOfPhases() === 3) { currentPerPhaseSampledValueTemplates = { - L1: chargingStation.getSampledValueTemplate( + L1: ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.CURRENT_IMPORT, OCPP16MeterValuePhase.L1 ), - L2: chargingStation.getSampledValueTemplate( + L2: ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.CURRENT_IMPORT, OCPP16MeterValuePhase.L2 ), - L3: chargingStation.getSampledValueTemplate( + L3: ChargingStationUtils.getSampledValueTemplate( + chargingStation, connectorId, OCPP16MeterValueMeasurand.CURRENT_IMPORT, OCPP16MeterValuePhase.L3 @@ -456,63 +441,85 @@ export class OCPP16ServiceUtils { OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER } measurand value`; const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; - let maximumAmperage: number; + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId); + let connectorMaximumAmperage: number; switch (chargingStation.getCurrentOutType()) { case CurrentType.AC: - maximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( + connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( chargingStation.getNumberOfPhases(), - chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider, + connectorMaximumAvailablePower, chargingStation.getVoltageOut() ); if (chargingStation.getNumberOfPhases() === 3) { const defaultFluctuatedAmperagePerPhase = currentSampledValueTemplate.value && Utils.getRandomFloatFluctuatedRounded( - parseInt(currentSampledValueTemplate.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + currentSampledValueTemplate.value, + connectorMaximumAmperage, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ); const phase1FluctuatedValue = currentPerPhaseSampledValueTemplates?.L1?.value && Utils.getRandomFloatFluctuatedRounded( - parseInt(currentPerPhaseSampledValueTemplates.L1.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + currentPerPhaseSampledValueTemplates.L1.value, + connectorMaximumAmperage, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ); const phase2FluctuatedValue = currentPerPhaseSampledValueTemplates?.L2?.value && Utils.getRandomFloatFluctuatedRounded( - parseInt(currentPerPhaseSampledValueTemplates.L2.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + currentPerPhaseSampledValueTemplates.L2.value, + connectorMaximumAmperage, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ); const phase3FluctuatedValue = currentPerPhaseSampledValueTemplates?.L3?.value && Utils.getRandomFloatFluctuatedRounded( - parseInt(currentPerPhaseSampledValueTemplates.L3.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + currentPerPhaseSampledValueTemplates.L3.value, + connectorMaximumAmperage, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ); currentMeasurandValues.L1 = phase1FluctuatedValue ?? defaultFluctuatedAmperagePerPhase ?? - Utils.getRandomFloatRounded(maximumAmperage); + Utils.getRandomFloatRounded(connectorMaximumAmperage); currentMeasurandValues.L2 = phase2FluctuatedValue ?? defaultFluctuatedAmperagePerPhase ?? - Utils.getRandomFloatRounded(maximumAmperage); + Utils.getRandomFloatRounded(connectorMaximumAmperage); currentMeasurandValues.L3 = phase3FluctuatedValue ?? defaultFluctuatedAmperagePerPhase ?? - Utils.getRandomFloatRounded(maximumAmperage); + Utils.getRandomFloatRounded(connectorMaximumAmperage); } else { currentMeasurandValues.L1 = currentSampledValueTemplate.value ? Utils.getRandomFloatFluctuatedRounded( - parseInt(currentSampledValueTemplate.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + currentSampledValueTemplate.value, + connectorMaximumAmperage, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumAmperage); + : Utils.getRandomFloatRounded(connectorMaximumAmperage); currentMeasurandValues.L2 = 0; currentMeasurandValues.L3 = 0; } @@ -523,17 +530,21 @@ export class OCPP16ServiceUtils { ); break; case CurrentType.DC: - maximumAmperage = DCElectricUtils.amperage( - chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider, + connectorMaximumAmperage = DCElectricUtils.amperage( + connectorMaximumAvailablePower, chargingStation.getVoltageOut() ); currentMeasurandValues.allPhases = currentSampledValueTemplate.value ? Utils.getRandomFloatFluctuatedRounded( - parseInt(currentSampledValueTemplate.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + currentSampledValueTemplate.value, + connectorMaximumAmperage, + { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() } + ), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumAmperage); + : Utils.getRandomFloatRounded(connectorMaximumAmperage); break; default: logger.error(errMsg); @@ -547,7 +558,8 @@ export class OCPP16ServiceUtils { ); const sampledValuesIndex = meterValue.sampledValue.length - 1; if ( - Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maximumAmperage || + Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > + connectorMaximumAmperage || debug ) { logger.error( @@ -556,7 +568,7 @@ export class OCPP16ServiceUtils { OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ meterValue.sampledValue[sampledValuesIndex].value - }/${maximumAmperage}` + }/${connectorMaximumAmperage}` ); } for ( @@ -577,7 +589,7 @@ export class OCPP16ServiceUtils { const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; if ( Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > - maximumAmperage || + connectorMaximumAmperage || debug ) { logger.error( @@ -588,13 +600,16 @@ export class OCPP16ServiceUtils { meterValue.sampledValue[sampledValuesPerPhaseIndex].phase }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ meterValue.sampledValue[sampledValuesPerPhaseIndex].value - }/${maximumAmperage}` + }/${connectorMaximumAmperage}` ); } } } // Energy.Active.Import.Register measurand (default) - const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); + const energySampledValueTemplate = ChargingStationUtils.getSampledValueTemplate( + chargingStation, + connectorId + ); if (energySampledValueTemplate) { OCPP16ServiceUtils.checkMeasurandPowerDivider( chargingStation, @@ -602,19 +617,26 @@ export class OCPP16ServiceUtils { ); const unitDivider = energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; - const maximumEnergyRounded = Utils.roundTo( - ((chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider) * - interval) / - (3600 * 1000), + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId); + const connectorMaximumEnergyRounded = Utils.roundTo( + (connectorMaximumAvailablePower * interval) / (3600 * 1000), 2 ); const energyValueRounded = energySampledValueTemplate.value ? // Cumulate the fluctuated value around the static one Utils.getRandomFloatFluctuatedRounded( - parseInt(energySampledValueTemplate.value), + OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( + energySampledValueTemplate.value, + connectorMaximumEnergyRounded, + { + limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(), + unitMultiplier: unitDivider, + } + ), energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumEnergyRounded); + : Utils.getRandomFloatRounded(connectorMaximumEnergyRounded); // Persist previous value on connector if ( connector && @@ -640,14 +662,14 @@ export class OCPP16ServiceUtils { ) ); const sampledValuesIndex = meterValue.sampledValue.length - 1; - if (energyValueRounded > maximumEnergyRounded || debug) { + if (energyValueRounded > connectorMaximumEnergyRounded || debug) { logger.error( `${chargingStation.logPrefix()} MeterValues measurand ${ meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER }: connectorId ${connectorId}, transaction ${ connector.transactionId - }, value: ${energyValueRounded}/${maximumEnergyRounded}, duration: ${Utils.roundTo( + }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo( interval / (3600 * 1000), 4 )}h` @@ -667,7 +689,10 @@ export class OCPP16ServiceUtils { sampledValue: [], }; // Energy.Active.Import.Register measurand (default) - const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); + const sampledValueTemplate = ChargingStationUtils.getSampledValueTemplate( + chargingStation, + connectorId + ); const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; meterValue.sampledValue.push( OCPP16ServiceUtils.buildSampledValue( @@ -689,7 +714,10 @@ export class OCPP16ServiceUtils { sampledValue: [], }; // Energy.Active.Import.Register measurand (default) - const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); + const sampledValueTemplate = ChargingStationUtils.getSampledValueTemplate( + chargingStation, + connectorId + ); const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; meterValue.sampledValue.push( OCPP16ServiceUtils.buildSampledValue( @@ -710,4 +738,80 @@ export class OCPP16ServiceUtils { meterValues.push(transactionEndMeterValue); return meterValues; } + + private static buildSampledValue( + sampledValueTemplate: SampledValueTemplate, + value: number, + context?: MeterValueContext, + phase?: OCPP16MeterValuePhase + ): OCPP16SampledValue { + const sampledValueValue = value ?? sampledValueTemplate?.value ?? null; + const sampledValueContext = context ?? sampledValueTemplate?.context ?? null; + const sampledValueLocation = + sampledValueTemplate?.location ?? + OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null); + const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null; + return { + ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && { + unit: sampledValueTemplate.unit, + }), + ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), + ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && { + measurand: sampledValueTemplate.measurand, + }), + ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), + ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }), + ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), + }; + } + + private static checkMeasurandPowerDivider( + chargingStation: ChargingStation, + measurandType: OCPP16MeterValueMeasurand + ): void { + if (Utils.isUndefined(chargingStation.powerDivider)) { + const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ + measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: powerDivider is undefined`; + logger.error(errMsg); + throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); + } else if (chargingStation?.powerDivider <= 0) { + const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ + measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: powerDivider have zero or below value ${chargingStation.powerDivider}`; + logger.error(errMsg); + throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); + } + } + + private static getMeasurandDefaultLocation( + measurandType: OCPP16MeterValueMeasurand + ): MeterValueLocation | undefined { + switch (measurandType) { + case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: + return MeterValueLocation.EV; + } + } + + private static getMeasurandDefaultUnit( + measurandType: OCPP16MeterValueMeasurand + ): MeterValueUnit | undefined { + switch (measurandType) { + case OCPP16MeterValueMeasurand.CURRENT_EXPORT: + case OCPP16MeterValueMeasurand.CURRENT_IMPORT: + case OCPP16MeterValueMeasurand.CURRENT_OFFERED: + return MeterValueUnit.AMP; + case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: + case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: + return MeterValueUnit.WATT_HOUR; + case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: + case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: + case OCPP16MeterValueMeasurand.POWER_OFFERED: + return MeterValueUnit.WATT; + case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: + return MeterValueUnit.PERCENT; + case OCPP16MeterValueMeasurand.VOLTAGE: + return MeterValueUnit.VOLT; + } + } }