-// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
-import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
-import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
-import MeasurandPerPhaseSampledValueTemplates, {
- SampledValueTemplate,
-} from '../../../types/MeasurandPerPhaseSampledValueTemplates';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import type { JSONSchemaType } from 'ajv';
+
+import type { ChargingStation } from '../../../charging-station';
+import { OCPPError } from '../../../exception';
import {
+ CurrentType,
+ ErrorType,
+ type JsonType,
+ type MeasurandPerPhaseSampledValueTemplates,
+ type MeasurandValues,
MeterValueContext,
MeterValueLocation,
MeterValueUnit,
- OCPP16MeterValue,
+ type OCPP16ChargingProfile,
+ type OCPP16IncomingRequestCommand,
+ type OCPP16MeterValue,
OCPP16MeterValueMeasurand,
OCPP16MeterValuePhase,
- OCPP16SampledValue,
-} from '../../../types/ocpp/1.6/MeterValues';
-import {
- OCPP16IncomingRequestCommand,
OCPP16RequestCommand,
-} from '../../../types/ocpp/1.6/Requests';
-import {
+ type OCPP16SampledValue,
OCPP16StandardParametersKey,
- OCPP16SupportedFeatureProfiles,
-} from '../../../types/ocpp/1.6/Configuration';
-
-import type ChargingStation from '../../ChargingStation';
-import Constants from '../../../utils/Constants';
-import { ErrorType } from '../../../types/ocpp/ErrorType';
-import MeasurandValues from '../../../types/MeasurandValues';
-import OCPPError from '../../../exception/OCPPError';
-import Utils from '../../../utils/Utils';
-import logger from '../../../utils/Logger';
-
-export class OCPP16ServiceUtils {
- public static checkMeasurandPowerDivider(
- 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);
- }
- }
+ type OCPP16SupportedFeatureProfiles,
+ OCPPVersion,
+ type SampledValueTemplate,
+ Voltage,
+} from '../../../types';
+import { ACElectricUtils, Constants, DCElectricUtils, Utils, logger } from '../../../utils';
+import { OCPPServiceUtils } from '../internal';
+export class OCPP16ServiceUtils extends OCPPServiceUtils {
public static checkFeatureProfile(
chargingStation: ChargingStation,
featureProfile: OCPP16SupportedFeatureProfiles,
return true;
}
- 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;
- }
- }
-
public static buildMeterValue(
chargingStation: ChargingStation,
connectorId: number,
debug = false
): OCPP16MeterValue {
const meterValue: OCPP16MeterValue = {
- timestamp: new Date().toISOString(),
+ timestamp: new Date(),
sampledValue: [],
};
const connector = chargingStation.getConnectorStatus(connectorId);
// SoC measurand
- const socSampledValueTemplate = chargingStation.getSampledValueTemplate(
+ const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.STATE_OF_CHARGE
);
if (socSampledValueTemplate) {
+ const socMaximumValue = 100;
+ const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0;
const socSampledValueTemplateValue = socSampledValueTemplate.value
? Utils.getRandomFloatFluctuatedRounded(
parseInt(socSampledValueTemplate.value),
socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : Utils.getRandomInteger(100);
+ : Utils.getRandomInteger(socMaximumValue, socMinimumValue);
meterValue.sampledValue.push(
OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue)
);
const sampledValuesIndex = meterValue.sampledValue.length - 1;
- if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
+ if (
+ Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue ||
+ Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue ||
+ debug
+ ) {
logger.error(
`${chargingStation.logPrefix()} MeterValues measurand ${
meterValue.sampledValue[sampledValuesIndex].measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
- }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+ }: connectorId ${connectorId}, transaction ${
+ connector?.transactionId
+ }, value: ${socMinimumValue}/${
meterValue.sampledValue[sampledValuesIndex].value
- }/100`
+ }/${socMaximumValue}}`
);
}
}
// Voltage measurand
- const voltageSampledValueTemplate = chargingStation.getSampledValueTemplate(
+ const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.VOLTAGE
);
) {
const phaseLineToNeutralValue = `L${phase}-N`;
const voltagePhaseLineToNeutralSampledValueTemplate =
- chargingStation.getSampledValueTemplate(
+ OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.VOLTAGE,
phaseLineToNeutralValue as OCPP16MeterValuePhase
OCPP16ServiceUtils.buildSampledValue(
voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
- null,
+ undefined,
phaseLineToNeutralValue as OCPP16MeterValuePhase
)
);
: chargingStation.getNumberOfPhases()
}`;
const voltagePhaseLineToLineSampledValueTemplate =
- chargingStation.getSampledValueTemplate(
+ OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.VOLTAGE,
phaseLineToLineValue as OCPP16MeterValuePhase
OCPP16ServiceUtils.buildSampledValue(
voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
- null,
+ undefined,
phaseLineToLineValue as OCPP16MeterValuePhase
)
);
}
}
// Power.Active.Import measurand
- const powerSampledValueTemplate = chargingStation.getSampledValueTemplate(
+ const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
);
let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
if (chargingStation.getNumberOfPhases() === 3) {
powerPerPhaseSampledValueTemplates = {
- L1: chargingStation.getSampledValueTemplate(
+ L1: OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
OCPP16MeterValuePhase.L1_N
),
- L2: chargingStation.getSampledValueTemplate(
+ L2: OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
OCPP16MeterValuePhase.L2_N
),
- L3: chargingStation.getSampledValueTemplate(
+ L3: OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
OCPP16MeterValuePhase.L3_N
chargingStation,
powerSampledValueTemplate.measurand
);
- const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
+ const errMsg = `MeterValues measurand ${
powerSampledValueTemplate.measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
}: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
} measurand value`;
const powerMeasurandValues = {} as MeasurandValues;
const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
- const connectorMaximumPower = Math.round(
- chargingStation.getConnectorMaximumAvailablePower(connectorId)
- );
+ const connectorMaximumAvailablePower =
+ chargingStation.getConnectorMaximumAvailablePower(connectorId);
+ const connectorMaximumPower = Math.round(connectorMaximumAvailablePower);
const connectorMaximumPowerPerPhase = Math.round(
- chargingStation.getConnectorMaximumAvailablePower(connectorId) /
- chargingStation.getNumberOfPhases()
+ connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
+ );
+ const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue) ?? 0;
+ const connectorMinimumPowerPerPhase = Math.round(
+ connectorMinimumPower / chargingStation.getNumberOfPhases()
);
switch (chargingStation.getCurrentOutType()) {
case CurrentType.AC:
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(connectorMaximumPowerPerPhase / unitDivider);
+ Utils.getRandomFloatRounded(
+ connectorMaximumPowerPerPhase / unitDivider,
+ connectorMinimumPowerPerPhase / unitDivider
+ );
powerMeasurandValues.L2 =
phase2FluctuatedValue ??
defaultFluctuatedPowerPerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
+ Utils.getRandomFloatRounded(
+ connectorMaximumPowerPerPhase / unitDivider,
+ connectorMinimumPowerPerPhase / unitDivider
+ );
powerMeasurandValues.L3 =
phase3FluctuatedValue ??
defaultFluctuatedPowerPerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
+ Utils.getRandomFloatRounded(
+ connectorMaximumPowerPerPhase / unitDivider,
+ connectorMinimumPowerPerPhase / 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(connectorMaximumPower / unitDivider);
+ : Utils.getRandomFloatRounded(
+ connectorMaximumPower / unitDivider,
+ connectorMinimumPower / unitDivider
+ );
powerMeasurandValues.L2 = 0;
powerMeasurandValues.L3 = 0;
}
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(connectorMaximumPower / unitDivider);
+ : Utils.getRandomFloatRounded(
+ connectorMaximumPower / unitDivider,
+ connectorMinimumPower / unitDivider
+ );
break;
default:
- logger.error(errMsg);
+ logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
}
meterValue.sampledValue.push(
);
const sampledValuesIndex = meterValue.sampledValue.length - 1;
const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2);
+ const connectorMinimumPowerRounded = Utils.roundTo(connectorMinimumPower / unitDivider, 2);
if (
Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
connectorMaximumPowerRounded ||
+ Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
+ connectorMinimumPowerRounded ||
debug
) {
logger.error(
`${chargingStation.logPrefix()} MeterValues measurand ${
meterValue.sampledValue[sampledValuesIndex].measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
- }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+ }: connectorId ${connectorId}, transaction ${
+ connector?.transactionId
+ }, value: ${connectorMinimumPowerRounded}/${
meterValue.sampledValue[sampledValuesIndex].value
}/${connectorMaximumPowerRounded}`
);
(powerPerPhaseSampledValueTemplates[`L${phase}`] as SampledValueTemplate) ??
powerSampledValueTemplate,
powerMeasurandValues[`L${phase}`] as number,
- null,
+ undefined,
phaseValue as OCPP16MeterValuePhase
)
);
connectorMaximumPowerPerPhase / unitDivider,
2
);
+ const connectorMinimumPowerPerPhaseRounded = Utils.roundTo(
+ connectorMinimumPowerPerPhase / unitDivider,
+ 2
+ );
if (
Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
connectorMaximumPowerPerPhaseRounded ||
+ Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
+ connectorMinimumPowerPerPhaseRounded ||
debug
) {
logger.error(
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
}: phase ${
meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
- }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+ }, connectorId ${connectorId}, transaction ${
+ connector?.transactionId
+ }, value: ${connectorMinimumPowerPerPhaseRounded}/${
meterValue.sampledValue[sampledValuesPerPhaseIndex].value
}/${connectorMaximumPowerPerPhaseRounded}`
);
}
}
// Current.Import measurand
- const currentSampledValueTemplate = chargingStation.getSampledValueTemplate(
+ const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.CURRENT_IMPORT
);
let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
if (chargingStation.getNumberOfPhases() === 3) {
currentPerPhaseSampledValueTemplates = {
- L1: chargingStation.getSampledValueTemplate(
+ L1: OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.CURRENT_IMPORT,
OCPP16MeterValuePhase.L1
),
- L2: chargingStation.getSampledValueTemplate(
+ L2: OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.CURRENT_IMPORT,
OCPP16MeterValuePhase.L2
),
- L3: chargingStation.getSampledValueTemplate(
+ L3: OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
connectorId,
OCPP16MeterValueMeasurand.CURRENT_IMPORT,
OCPP16MeterValuePhase.L3
chargingStation,
currentSampledValueTemplate.measurand
);
- const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
+ const errMsg = `MeterValues measurand ${
currentSampledValueTemplate.measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
}: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
} measurand value`;
const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
+ const connectorMaximumAvailablePower =
+ chargingStation.getConnectorMaximumAvailablePower(connectorId);
+ const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
let connectorMaximumAmperage: number;
switch (chargingStation.getCurrentOutType()) {
case CurrentType.AC:
connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
chargingStation.getNumberOfPhases(),
- chargingStation.getConnectorMaximumAvailablePower(connectorId),
+ 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(connectorMaximumAmperage);
+ Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
currentMeasurandValues.L2 =
phase2FluctuatedValue ??
defaultFluctuatedAmperagePerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumAmperage);
+ Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
currentMeasurandValues.L3 =
phase3FluctuatedValue ??
defaultFluctuatedAmperagePerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumAmperage);
+ Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
} 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(connectorMaximumAmperage);
+ : Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
currentMeasurandValues.L2 = 0;
currentMeasurandValues.L3 = 0;
}
break;
case CurrentType.DC:
connectorMaximumAmperage = DCElectricUtils.amperage(
- chargingStation.getConnectorMaximumAvailablePower(connectorId),
+ 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(connectorMaximumAmperage);
+ : Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
break;
default:
- logger.error(errMsg);
+ logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
}
meterValue.sampledValue.push(
if (
Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
connectorMaximumAmperage ||
+ Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
+ connectorMinimumAmperage ||
debug
) {
logger.error(
`${chargingStation.logPrefix()} MeterValues measurand ${
meterValue.sampledValue[sampledValuesIndex].measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
- }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+ }: connectorId ${connectorId}, transaction ${
+ connector?.transactionId
+ }, value: ${connectorMinimumAmperage}/${
meterValue.sampledValue[sampledValuesIndex].value
}/${connectorMaximumAmperage}`
);
(currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
currentSampledValueTemplate,
currentMeasurandValues[phaseValue] as number,
- null,
+ undefined,
phaseValue as OCPP16MeterValuePhase
)
);
if (
Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
connectorMaximumAmperage ||
+ Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
+ connectorMinimumAmperage ||
debug
) {
logger.error(
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
}: phase ${
meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
- }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
+ }, connectorId ${connectorId}, transaction ${
+ connector?.transactionId
+ }, value: ${connectorMinimumAmperage}/${
meterValue.sampledValue[sampledValuesPerPhaseIndex].value
}/${connectorMaximumAmperage}`
);
}
}
// Energy.Active.Import.Register measurand (default)
- const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+ const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
+ connectorId
+ );
if (energySampledValueTemplate) {
OCPP16ServiceUtils.checkMeasurandPowerDivider(
chargingStation,
);
const unitDivider =
energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
+ const connectorMaximumAvailablePower =
+ chargingStation.getConnectorMaximumAvailablePower(connectorId);
const connectorMaximumEnergyRounded = Utils.roundTo(
- (chargingStation.getConnectorMaximumAvailablePower(connectorId) * interval) / (3600 * 1000),
+ (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(connectorMaximumEnergyRounded);
// Persist previous value on connector
if (
connector &&
- !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) &&
+ Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
connector.energyActiveImportRegisterValue >= 0 &&
- !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) &&
+ Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
connector.transactionEnergyActiveImportRegisterValue >= 0
) {
connector.energyActiveImportRegisterValue += energyValueRounded;
meterValue.sampledValue[sampledValuesIndex].measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
}: connectorId ${connectorId}, transaction ${
- connector.transactionId
+ connector?.transactionId
}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
interval / (3600 * 1000),
4
meterStart: number
): OCPP16MeterValue {
const meterValue: OCPP16MeterValue = {
- timestamp: new Date().toISOString(),
+ timestamp: new Date(),
sampledValue: [],
};
// Energy.Active.Import.Register measurand (default)
- const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+ const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
+ connectorId
+ );
const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
meterValue.sampledValue.push(
OCPP16ServiceUtils.buildSampledValue(
sampledValueTemplate,
- Utils.roundTo(meterStart / unitDivider, 4),
+ Utils.roundTo((meterStart ?? 0) / unitDivider, 4),
MeterValueContext.TRANSACTION_BEGIN
)
);
meterStop: number
): OCPP16MeterValue {
const meterValue: OCPP16MeterValue = {
- timestamp: new Date().toISOString(),
+ timestamp: new Date(),
sampledValue: [],
};
// Energy.Active.Import.Register measurand (default)
- const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+ const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+ chargingStation,
+ connectorId
+ );
const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
meterValue.sampledValue.push(
OCPP16ServiceUtils.buildSampledValue(
sampledValueTemplate,
- Utils.roundTo(meterStop / unitDivider, 4),
+ Utils.roundTo((meterStop ?? 0) / unitDivider, 4),
MeterValueContext.TRANSACTION_END
)
);
meterValues.push(transactionEndMeterValue);
return meterValues;
}
+
+ public static setChargingProfile(
+ chargingStation: ChargingStation,
+ connectorId: number,
+ cp: OCPP16ChargingProfile
+ ): void {
+ if (
+ Utils.isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)
+ ) {
+ logger.error(
+ `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
+ );
+ chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
+ }
+ if (
+ Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
+ ) {
+ logger.error(
+ `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
+ );
+ chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
+ }
+ let cpReplaced = false;
+ if (Utils.isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
+ chargingStation
+ .getConnectorStatus(connectorId)
+ ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
+ if (
+ chargingProfile.chargingProfileId === cp.chargingProfileId ||
+ (chargingProfile.stackLevel === cp.stackLevel &&
+ chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
+ ) {
+ chargingStation.getConnectorStatus(connectorId).chargingProfiles[index] = cp;
+ cpReplaced = true;
+ }
+ });
+ }
+ !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
+ }
+
+ public static parseJsonSchemaFile<T extends JsonType>(
+ relativePath: string,
+ moduleName?: string,
+ methodName?: string
+ ): JSONSchemaType<T> {
+ return super.parseJsonSchemaFile<T>(
+ path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
+ OCPPVersion.VERSION_16,
+ moduleName,
+ methodName
+ );
+ }
+
+ 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 = `MeterValues measurand ${
+ measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+ }: powerDivider is undefined`;
+ logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
+ throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
+ } else if (chargingStation?.powerDivider <= 0) {
+ const errMsg = `MeterValues measurand ${
+ measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+ }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
+ logger.error(`${chargingStation.logPrefix()} ${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;
+ }
+ }
}