From: Jérôme Benoit Date: Wed, 1 Apr 2026 23:50:56 +0000 (+0200) Subject: refactor(ocpp): consolidate MeterValue builders into shared core X-Git-Tag: ocpp-server@v4.2.0~9 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=5ab61a5f7a55b8f71543d7ae85cf3e1725d4516b;p=e-mobility-charging-stations-simulator.git refactor(ocpp): consolidate MeterValue builders into shared core Merge buildOCPP16MeterValue and buildOCPP20MeterValue into buildMeterValue, eliminating ~250 lines of duplicated logic. Version differences resolved via switch: evseId + sampled value builder callback. --- diff --git a/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts b/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts index 09491502..f0f63d31 100644 --- a/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts +++ b/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts @@ -1,41 +1,12 @@ -import type { ChargingStation } from '../../../charging-station/index.js' - -import { OCPPError } from '../../../exception/index.js' import { type ChargingStationInfo, - type ConfigurationKeyType, - CurrentType, - ErrorType, - type MeasurandPerPhaseSampledValueTemplates, - type MeasurandValues, type MeterValueContext, type MeterValuePhase, - MeterValueUnit, type OCPP16BootNotificationRequest, - type OCPP16MeterValue, type OCPP16SampledValue, - RequestCommand, type SampledValueTemplate, } from '../../../types/index.js' -import { ACElectricUtils, DCElectricUtils, roundTo } from '../../../utils/index.js' -import { - addLineToLineVoltageToMeterValue, - addMainVoltageToMeterValue, - addPhaseVoltageToMeterValue, - buildCurrentMeasurandValue, - buildEmptyMeterValue, - buildEnergyMeasurandValue, - buildPowerMeasurandValue, - buildSocMeasurandValue, - buildVoltageMeasurandValue, - resolveSampledValueFields, - updateConnectorEnergyValues, - validateCurrentMeasurandPhaseValue, - validateCurrentMeasurandValue, - validateEnergyMeasurandValue, - validatePowerMeasurandValue, - validateSocMeasurandValue, -} from '../OCPPServiceUtils.js' +import { resolveSampledValueFields } from '../OCPPServiceUtils.js' export const buildOCPP16BootNotificationRequest = ( stationInfo: ChargingStationInfo @@ -61,263 +32,6 @@ export const buildOCPP16BootNotificationRequest = ( }), }) -export const buildOCPP16MeterValue = ( - chargingStation: ChargingStation, - transactionId: number | string, - interval: number, - measurandsKey?: ConfigurationKeyType, - context?: MeterValueContext, - debug = false -): OCPP16MeterValue => { - const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId) - if (connectorId == null) { - throw new OCPPError( - ErrorType.INTERNAL_ERROR, - `Cannot build MeterValues: no connector found for transaction ${String(transactionId)}`, - RequestCommand.METER_VALUES - ) - } - const connectorStatus = chargingStation.getConnectorStatus(connectorId) - const meterValue = buildEmptyMeterValue() as OCPP16MeterValue - const buildVersionedSampledValue = ( - sampledValueTemplate: SampledValueTemplate, - value: number, - context?: MeterValueContext, - phase?: MeterValuePhase - ): OCPP16SampledValue => { - return buildOCPP16SampledValue(sampledValueTemplate, value, context, phase) - } - // SoC measurand - const socMeasurand = buildSocMeasurandValue( - chargingStation, - connectorId, - undefined, - measurandsKey - ) - if (socMeasurand != null) { - const socSampledValue = buildVersionedSampledValue( - socMeasurand.template, - socMeasurand.value, - context - ) - meterValue.sampledValue.push(socSampledValue) - validateSocMeasurandValue( - chargingStation, - connectorId, - socSampledValue, - socMeasurand.template.minimumValue ?? 0, - 100, - debug - ) - } - // Voltage measurand - const voltageMeasurand = buildVoltageMeasurandValue( - chargingStation, - connectorId, - undefined, - measurandsKey - ) - if (voltageMeasurand != null) { - addMainVoltageToMeterValue( - chargingStation, - meterValue, - voltageMeasurand, - buildVersionedSampledValue, - context - ) - for ( - let phase = 1; - chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); - phase++ - ) { - addPhaseVoltageToMeterValue( - chargingStation, - connectorId, - meterValue, - voltageMeasurand, - phase, - buildVersionedSampledValue, - measurandsKey, - context - ) - addLineToLineVoltageToMeterValue( - chargingStation, - connectorId, - meterValue, - voltageMeasurand, - phase, - buildVersionedSampledValue, - measurandsKey, - context - ) - } - } - // Power.Active.Import measurand - const powerMeasurand = buildPowerMeasurandValue( - chargingStation, - connectorId, - undefined, - measurandsKey - ) - if (powerMeasurand?.values.allPhases != null) { - const unitDivider = powerMeasurand.template.unit === MeterValueUnit.KILO_WATT ? 1000 : 1 - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId) - const connectorMaximumPower = Math.round(connectorMaximumAvailablePower) - const connectorMinimumPower = Math.round(powerMeasurand.template.minimumValue ?? 0) - - meterValue.sampledValue.push( - buildVersionedSampledValue(powerMeasurand.template, powerMeasurand.values.allPhases, context) - ) - const sampledValuesIndex = meterValue.sampledValue.length - 1 - validatePowerMeasurandValue( - chargingStation, - connectorId, - connectorStatus, - meterValue.sampledValue[sampledValuesIndex], - connectorMaximumPower / unitDivider, - connectorMinimumPower / unitDivider, - debug - ) - if (chargingStation.getNumberOfPhases() === 3) { - const connectorMaximumPowerPerPhase = Math.round( - connectorMaximumAvailablePower / chargingStation.getNumberOfPhases() - ) - const connectorMinimumPowerPerPhase = Math.round( - connectorMinimumPower / chargingStation.getNumberOfPhases() - ) - for (let phase = 1; phase <= chargingStation.getNumberOfPhases(); phase++) { - const phaseTemplate = - powerMeasurand.perPhaseTemplates[ - `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates - ] - if (phaseTemplate != null) { - const phaseValue = `L${phase.toString()}-N` as MeterValuePhase - const phasePowerValue = - powerMeasurand.values[`L${phase.toString()}` as keyof MeasurandValues] - meterValue.sampledValue.push( - buildVersionedSampledValue(phaseTemplate, phasePowerValue, context, phaseValue) - ) - const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1 - validatePowerMeasurandValue( - chargingStation, - connectorId, - connectorStatus, - meterValue.sampledValue[sampledValuesPerPhaseIndex], - connectorMaximumPowerPerPhase / unitDivider, - connectorMinimumPowerPerPhase / unitDivider, - debug - ) - } - } - } - } - // Current.Import measurand - const currentMeasurand = buildCurrentMeasurandValue( - chargingStation, - connectorId, - undefined, - measurandsKey - ) - if (currentMeasurand?.values.allPhases != null) { - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId) - const connectorMaximumAmperage = - chargingStation.stationInfo?.currentOutType === CurrentType.AC - ? ACElectricUtils.amperagePerPhaseFromPower( - chargingStation.getNumberOfPhases(), - connectorMaximumAvailablePower, - chargingStation.getVoltageOut() - ) - : DCElectricUtils.amperage(connectorMaximumAvailablePower, chargingStation.getVoltageOut()) - const connectorMinimumAmperage = currentMeasurand.template.minimumValue ?? 0 - - meterValue.sampledValue.push( - buildVersionedSampledValue( - currentMeasurand.template, - currentMeasurand.values.allPhases, - context - ) - ) - const sampledValuesIndex = meterValue.sampledValue.length - 1 - validateCurrentMeasurandValue( - chargingStation, - connectorId, - connectorStatus, - meterValue.sampledValue[sampledValuesIndex], - connectorMaximumAmperage, - connectorMinimumAmperage, - debug - ) - for ( - let phase = 1; - chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); - phase++ - ) { - const phaseValue = `L${phase.toString()}` as MeterValuePhase - meterValue.sampledValue.push( - buildVersionedSampledValue( - currentMeasurand.perPhaseTemplates[ - phaseValue as keyof MeasurandPerPhaseSampledValueTemplates - ] ?? currentMeasurand.template, - currentMeasurand.values[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates], - context, - phaseValue - ) - ) - const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1 - validateCurrentMeasurandPhaseValue( - chargingStation, - connectorId, - connectorStatus, - meterValue.sampledValue[sampledValuesPerPhaseIndex], - connectorMaximumAmperage, - connectorMinimumAmperage, - debug - ) - } - } - // Energy.Active.Import.Register measurand (default) - const energyMeasurand = buildEnergyMeasurandValue( - chargingStation, - connectorId, - interval, - undefined, - measurandsKey - ) - if (energyMeasurand != null) { - updateConnectorEnergyValues(connectorStatus, energyMeasurand.value) - const unitDivider = energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1 - const energySampledValue = buildVersionedSampledValue( - energyMeasurand.template, - roundTo( - chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider, - 2 - ), - context - ) - meterValue.sampledValue.push(energySampledValue) - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId) - const connectorMaximumEnergyRounded = roundTo( - (connectorMaximumAvailablePower * interval) / (3600 * 1000), - 2 - ) - const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2) - validateEnergyMeasurandValue( - chargingStation, - connectorId, - energySampledValue, - energyMeasurand.value, - connectorMinimumEnergyRounded, - connectorMaximumEnergyRounded, - interval, - debug - ) - } - return meterValue -} - /** * Builds an OCPP 1.6 sampled value from a template and measurement data. * @param sampledValueTemplate - The sampled value template to use. diff --git a/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts b/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts index 274108f6..4033bff5 100644 --- a/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts +++ b/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts @@ -1,46 +1,18 @@ -import type { ChargingStation } from '../../../charging-station/index.js' import type { StopTransactionReason } from '../../../types/index.js' -import { OCPPError } from '../../../exception/index.js' import { BootReasonEnumType, type ChargingStationInfo, - type ConfigurationKeyType, - CurrentType, - ErrorType, - type MeasurandPerPhaseSampledValueTemplates, - type MeasurandValues, type MeterValueContext, type MeterValuePhase, - MeterValueUnit, OCPP16StopTransactionReason, type OCPP20BootNotificationRequest, - type OCPP20MeterValue, OCPP20ReasonEnumType, type OCPP20SampledValue, OCPP20TriggerReasonEnumType, - RequestCommand, type SampledValueTemplate, } from '../../../types/index.js' -import { ACElectricUtils, DCElectricUtils, roundTo } from '../../../utils/index.js' -import { - addLineToLineVoltageToMeterValue, - addMainVoltageToMeterValue, - addPhaseVoltageToMeterValue, - buildCurrentMeasurandValue, - buildEmptyMeterValue, - buildEnergyMeasurandValue, - buildPowerMeasurandValue, - buildSocMeasurandValue, - buildVoltageMeasurandValue, - resolveSampledValueFields, - updateConnectorEnergyValues, - validateCurrentMeasurandPhaseValue, - validateCurrentMeasurandValue, - validateEnergyMeasurandValue, - validatePowerMeasurandValue, - validateSocMeasurandValue, -} from '../OCPPServiceUtils.js' +import { resolveSampledValueFields } from '../OCPPServiceUtils.js' export const buildOCPP20BootNotificationRequest = ( stationInfo: ChargingStationInfo, @@ -65,259 +37,6 @@ export const buildOCPP20BootNotificationRequest = ( reason: bootReason, }) -export const buildOCPP20MeterValue = ( - chargingStation: ChargingStation, - transactionId: number | string, - interval: number, - measurandsKey?: ConfigurationKeyType, - context?: MeterValueContext, - debug = false -): OCPP20MeterValue => { - const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId) - const evseId = chargingStation.getEvseIdByTransactionId(transactionId) - if (connectorId == null || evseId == null) { - throw new OCPPError( - ErrorType.INTERNAL_ERROR, - `Cannot build MeterValues: no connector/EVSE found for transaction ${String(transactionId)}`, - RequestCommand.METER_VALUES - ) - } - const connectorStatus = chargingStation.getConnectorStatus(connectorId) - const meterValue = buildEmptyMeterValue() as OCPP20MeterValue - const buildVersionedSampledValue = ( - sampledValueTemplate: SampledValueTemplate, - value: number, - context?: MeterValueContext, - phase?: MeterValuePhase - ): OCPP20SampledValue => { - return buildOCPP20SampledValue(sampledValueTemplate, value, context, phase) - } - // SoC measurand - const socMeasurand = buildSocMeasurandValue(chargingStation, connectorId, evseId, measurandsKey) - if (socMeasurand != null) { - const socSampledValue = buildVersionedSampledValue( - socMeasurand.template, - socMeasurand.value, - context - ) - meterValue.sampledValue.push(socSampledValue) - validateSocMeasurandValue( - chargingStation, - connectorId, - socSampledValue, - socMeasurand.template.minimumValue ?? 0, - 100, - debug - ) - } - // Voltage measurand - const voltageMeasurand = buildVoltageMeasurandValue( - chargingStation, - connectorId, - evseId, - measurandsKey - ) - if (voltageMeasurand != null) { - addMainVoltageToMeterValue( - chargingStation, - meterValue, - voltageMeasurand, - buildVersionedSampledValue, - context - ) - for ( - let phase = 1; - chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); - phase++ - ) { - addPhaseVoltageToMeterValue( - chargingStation, - connectorId, - meterValue, - voltageMeasurand, - phase, - buildVersionedSampledValue, - measurandsKey, - context - ) - addLineToLineVoltageToMeterValue( - chargingStation, - connectorId, - meterValue, - voltageMeasurand, - phase, - buildVersionedSampledValue, - measurandsKey, - context - ) - } - } - // Power.Active.Import measurand - const powerMeasurand = buildPowerMeasurandValue( - chargingStation, - connectorId, - evseId, - measurandsKey - ) - if (powerMeasurand?.values.allPhases != null) { - const unitDivider = powerMeasurand.template.unit === MeterValueUnit.KILO_WATT ? 1000 : 1 - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId) - const connectorMaximumPower = Math.round(connectorMaximumAvailablePower) - const connectorMinimumPower = Math.round(powerMeasurand.template.minimumValue ?? 0) - - meterValue.sampledValue.push( - buildVersionedSampledValue(powerMeasurand.template, powerMeasurand.values.allPhases, context) - ) - const sampledValuesIndex = meterValue.sampledValue.length - 1 - validatePowerMeasurandValue( - chargingStation, - connectorId, - connectorStatus, - meterValue.sampledValue[sampledValuesIndex], - connectorMaximumPower / unitDivider, - connectorMinimumPower / unitDivider, - debug - ) - if (chargingStation.getNumberOfPhases() === 3) { - const connectorMaximumPowerPerPhase = Math.round( - connectorMaximumAvailablePower / chargingStation.getNumberOfPhases() - ) - const connectorMinimumPowerPerPhase = Math.round( - connectorMinimumPower / chargingStation.getNumberOfPhases() - ) - for (let phase = 1; phase <= chargingStation.getNumberOfPhases(); phase++) { - const phaseTemplate = - powerMeasurand.perPhaseTemplates[ - `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates - ] - if (phaseTemplate != null) { - const phaseValue = `L${phase.toString()}-N` as MeterValuePhase - const phasePowerValue = - powerMeasurand.values[`L${phase.toString()}` as keyof MeasurandValues] - meterValue.sampledValue.push( - buildVersionedSampledValue(phaseTemplate, phasePowerValue, context, phaseValue) - ) - const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1 - validatePowerMeasurandValue( - chargingStation, - connectorId, - connectorStatus, - meterValue.sampledValue[sampledValuesPerPhaseIndex], - connectorMaximumPowerPerPhase / unitDivider, - connectorMinimumPowerPerPhase / unitDivider, - debug - ) - } - } - } - } - // Current.Import measurand - const currentMeasurand = buildCurrentMeasurandValue( - chargingStation, - connectorId, - evseId, - measurandsKey - ) - if (currentMeasurand?.values.allPhases != null) { - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId) - const connectorMaximumAmperage = - chargingStation.stationInfo?.currentOutType === CurrentType.AC - ? ACElectricUtils.amperagePerPhaseFromPower( - chargingStation.getNumberOfPhases(), - connectorMaximumAvailablePower, - chargingStation.getVoltageOut() - ) - : DCElectricUtils.amperage(connectorMaximumAvailablePower, chargingStation.getVoltageOut()) - const connectorMinimumAmperage = currentMeasurand.template.minimumValue ?? 0 - - meterValue.sampledValue.push( - buildVersionedSampledValue( - currentMeasurand.template, - currentMeasurand.values.allPhases, - context - ) - ) - const sampledValuesIndex = meterValue.sampledValue.length - 1 - validateCurrentMeasurandValue( - chargingStation, - connectorId, - connectorStatus, - meterValue.sampledValue[sampledValuesIndex], - connectorMaximumAmperage, - connectorMinimumAmperage, - debug - ) - for ( - let phase = 1; - chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); - phase++ - ) { - const phaseValue = `L${phase.toString()}` as MeterValuePhase - meterValue.sampledValue.push( - buildVersionedSampledValue( - currentMeasurand.perPhaseTemplates[ - phaseValue as keyof MeasurandPerPhaseSampledValueTemplates - ] ?? currentMeasurand.template, - currentMeasurand.values[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates], - context, - phaseValue - ) - ) - const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1 - validateCurrentMeasurandPhaseValue( - chargingStation, - connectorId, - connectorStatus, - meterValue.sampledValue[sampledValuesPerPhaseIndex], - connectorMaximumAmperage, - connectorMinimumAmperage, - debug - ) - } - } - // Energy.Active.Import.Register measurand (default) - const energyMeasurand = buildEnergyMeasurandValue( - chargingStation, - connectorId, - interval, - evseId, - measurandsKey - ) - if (energyMeasurand != null) { - updateConnectorEnergyValues(connectorStatus, energyMeasurand.value) - const unitDivider = energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1 - const energySampledValue = buildVersionedSampledValue( - energyMeasurand.template, - roundTo( - chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider, - 2 - ), - context - ) - meterValue.sampledValue.push(energySampledValue) - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId) - const connectorMaximumEnergyRounded = roundTo( - (connectorMaximumAvailablePower * interval) / (3600 * 1000), - 2 - ) - const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2) - validateEnergyMeasurandValue( - chargingStation, - connectorId, - energySampledValue, - energyMeasurand.value, - connectorMinimumEnergyRounded, - connectorMaximumEnergyRounded, - interval, - debug - ) - } - return meterValue -} - /** * Builds an OCPP 2.0 sampled value from a template and measurement data. * @param sampledValueTemplate - The sampled value template to use. diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index 4cedebb0..533241cc 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -54,11 +54,11 @@ import { } from '../../utils/index.js' import { buildOCPP16BootNotificationRequest, - buildOCPP16MeterValue, + buildOCPP16SampledValue, } from './1.6/OCPP16RequestBuilders.js' import { buildOCPP20BootNotificationRequest, - buildOCPP20MeterValue, + buildOCPP20SampledValue, } from './2.0/OCPP20RequestBuilders.js' import { OCPPConstants } from './OCPPConstants.js' @@ -988,26 +988,37 @@ export const buildMeterValue = ( if (transactionId == null) { return buildEmptyMeterValue() } + const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId) + let evseId: number | undefined + let buildVersionedSampledValue: ( + sampledValueTemplate: SampledValueTemplate, + value: number, + context?: MeterValueContext, + phase?: MeterValuePhase + ) => SampledValue switch (chargingStation.stationInfo?.ocppVersion) { case OCPPVersion.VERSION_16: - return buildOCPP16MeterValue( - chargingStation, - transactionId, - interval, - measurandsKey, - context, - debug - ) + if (connectorId == null) { + throw new OCPPError( + ErrorType.INTERNAL_ERROR, + `Cannot build MeterValues: no connector found for transaction ${String(transactionId)}`, + RequestCommand.METER_VALUES + ) + } + buildVersionedSampledValue = buildOCPP16SampledValue + break case OCPPVersion.VERSION_20: case OCPPVersion.VERSION_201: - return buildOCPP20MeterValue( - chargingStation, - transactionId, - interval, - measurandsKey, - context, - debug - ) + evseId = chargingStation.getEvseIdByTransactionId(transactionId) + if (connectorId == null || evseId == null) { + throw new OCPPError( + ErrorType.INTERNAL_ERROR, + `Cannot build MeterValues: no connector/EVSE found for transaction ${String(transactionId)}`, + RequestCommand.METER_VALUES + ) + } + buildVersionedSampledValue = buildOCPP20SampledValue + break default: throw new OCPPError( ErrorType.INTERNAL_ERROR, @@ -1016,6 +1027,232 @@ export const buildMeterValue = ( RequestCommand.METER_VALUES ) } + const connectorStatus = chargingStation.getConnectorStatus(connectorId) + const meterValue: { sampledValue: SampledValue[]; timestamp: Date } = buildEmptyMeterValue() + // SoC measurand + const socMeasurand = buildSocMeasurandValue(chargingStation, connectorId, evseId, measurandsKey) + if (socMeasurand != null) { + const socSampledValue = buildVersionedSampledValue( + socMeasurand.template, + socMeasurand.value, + context + ) + meterValue.sampledValue.push(socSampledValue) + validateSocMeasurandValue( + chargingStation, + connectorId, + socSampledValue, + socMeasurand.template.minimumValue ?? 0, + 100, + debug + ) + } + // Voltage measurand + const voltageMeasurand = buildVoltageMeasurandValue( + chargingStation, + connectorId, + evseId, + measurandsKey + ) + if (voltageMeasurand != null) { + addMainVoltageToMeterValue( + chargingStation, + meterValue, + voltageMeasurand, + buildVersionedSampledValue, + context + ) + for ( + let phase = 1; + chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); + phase++ + ) { + addPhaseVoltageToMeterValue( + chargingStation, + connectorId, + meterValue, + voltageMeasurand, + phase, + buildVersionedSampledValue, + measurandsKey, + context + ) + addLineToLineVoltageToMeterValue( + chargingStation, + connectorId, + meterValue, + voltageMeasurand, + phase, + buildVersionedSampledValue, + measurandsKey, + context + ) + } + } + // Power.Active.Import measurand + const powerMeasurand = buildPowerMeasurandValue( + chargingStation, + connectorId, + evseId, + measurandsKey + ) + if (powerMeasurand?.values.allPhases != null) { + const unitDivider = powerMeasurand.template.unit === MeterValueUnit.KILO_WATT ? 1000 : 1 + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId) + const connectorMaximumPower = Math.round(connectorMaximumAvailablePower) + const connectorMinimumPower = Math.round(powerMeasurand.template.minimumValue ?? 0) + + meterValue.sampledValue.push( + buildVersionedSampledValue(powerMeasurand.template, powerMeasurand.values.allPhases, context) + ) + const sampledValuesIndex = meterValue.sampledValue.length - 1 + validatePowerMeasurandValue( + chargingStation, + connectorId, + connectorStatus, + meterValue.sampledValue[sampledValuesIndex], + connectorMaximumPower / unitDivider, + connectorMinimumPower / unitDivider, + debug + ) + if (chargingStation.getNumberOfPhases() === 3) { + const connectorMaximumPowerPerPhase = Math.round( + connectorMaximumAvailablePower / chargingStation.getNumberOfPhases() + ) + const connectorMinimumPowerPerPhase = Math.round( + connectorMinimumPower / chargingStation.getNumberOfPhases() + ) + for (let phase = 1; phase <= chargingStation.getNumberOfPhases(); phase++) { + const phaseTemplate = + powerMeasurand.perPhaseTemplates[ + `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates + ] + if (phaseTemplate != null) { + const phaseValue = `L${phase.toString()}-N` as MeterValuePhase + const phasePowerValue = + powerMeasurand.values[`L${phase.toString()}` as keyof MeasurandValues] + meterValue.sampledValue.push( + buildVersionedSampledValue(phaseTemplate, phasePowerValue, context, phaseValue) + ) + const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1 + validatePowerMeasurandValue( + chargingStation, + connectorId, + connectorStatus, + meterValue.sampledValue[sampledValuesPerPhaseIndex], + connectorMaximumPowerPerPhase / unitDivider, + connectorMinimumPowerPerPhase / unitDivider, + debug + ) + } + } + } + } + // Current.Import measurand + const currentMeasurand = buildCurrentMeasurandValue( + chargingStation, + connectorId, + evseId, + measurandsKey + ) + if (currentMeasurand?.values.allPhases != null) { + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId) + const connectorMaximumAmperage = + chargingStation.stationInfo.currentOutType === CurrentType.AC + ? ACElectricUtils.amperagePerPhaseFromPower( + chargingStation.getNumberOfPhases(), + connectorMaximumAvailablePower, + chargingStation.getVoltageOut() + ) + : DCElectricUtils.amperage(connectorMaximumAvailablePower, chargingStation.getVoltageOut()) + const connectorMinimumAmperage = currentMeasurand.template.minimumValue ?? 0 + + meterValue.sampledValue.push( + buildVersionedSampledValue( + currentMeasurand.template, + currentMeasurand.values.allPhases, + context + ) + ) + const sampledValuesIndex = meterValue.sampledValue.length - 1 + validateCurrentMeasurandValue( + chargingStation, + connectorId, + connectorStatus, + meterValue.sampledValue[sampledValuesIndex], + connectorMaximumAmperage, + connectorMinimumAmperage, + debug + ) + for ( + let phase = 1; + chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); + phase++ + ) { + const phaseValue = `L${phase.toString()}` as MeterValuePhase + meterValue.sampledValue.push( + buildVersionedSampledValue( + currentMeasurand.perPhaseTemplates[ + phaseValue as keyof MeasurandPerPhaseSampledValueTemplates + ] ?? currentMeasurand.template, + currentMeasurand.values[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates], + context, + phaseValue + ) + ) + const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1 + validateCurrentMeasurandPhaseValue( + chargingStation, + connectorId, + connectorStatus, + meterValue.sampledValue[sampledValuesPerPhaseIndex], + connectorMaximumAmperage, + connectorMinimumAmperage, + debug + ) + } + } + // Energy.Active.Import.Register measurand (default) + const energyMeasurand = buildEnergyMeasurandValue( + chargingStation, + connectorId, + interval, + evseId, + measurandsKey + ) + if (energyMeasurand != null) { + updateConnectorEnergyValues(connectorStatus, energyMeasurand.value) + const unitDivider = energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1 + const energySampledValue = buildVersionedSampledValue( + energyMeasurand.template, + roundTo( + chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider, + 2 + ), + context + ) + meterValue.sampledValue.push(energySampledValue) + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId) + const connectorMaximumEnergyRounded = roundTo( + (connectorMaximumAvailablePower * interval) / (3600 * 1000), + 2 + ) + const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2) + validateEnergyMeasurandValue( + chargingStation, + connectorId, + energySampledValue, + energyMeasurand.value, + connectorMinimumEnergyRounded, + connectorMaximumEnergyRounded, + interval, + debug + ) + } + return meterValue as MeterValue } const checkMeasurandPowerDivider = (