From 9c4a5f23dc953f21f6c24fe6d66d323b3ea63712 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 1 Apr 2026 18:17:13 +0200 Subject: [PATCH] refactor(ocpp): consolidate meter value builders into RequestBuilders Merge OCPP16MeterValueBuilders.ts and OCPP20MeterValueBuilders.ts into their respective OCPP16RequestBuilders.ts and OCPP20RequestBuilders.ts files. Version-specific pure builders belong in a single leaf module per stack, not separate files per builder type. --- .../ocpp/1.6/OCPP16MeterValueBuilders.ts | 303 ----------------- .../ocpp/1.6/OCPP16RequestBuilders.ts | 306 +++++++++++++++++- .../ocpp/2.0/OCPP20MeterValueBuilders.ts | 212 ------------ .../ocpp/2.0/OCPP20RequestBuilders.ts | 210 ++++++++++++ src/charging-station/ocpp/OCPPServiceUtils.ts | 12 +- 5 files changed, 523 insertions(+), 520 deletions(-) delete mode 100644 src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts delete mode 100644 src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts diff --git a/src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts b/src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts deleted file mode 100644 index c51ab485..00000000 --- a/src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts +++ /dev/null @@ -1,303 +0,0 @@ -import type { ChargingStation } from '../../../charging-station/index.js' - -import { OCPPError } from '../../../exception/index.js' -import { - type ConfigurationKeyType, - CurrentType, - ErrorType, - type MeasurandPerPhaseSampledValueTemplates, - type MeasurandValues, - type MeterValueContext, - type MeterValuePhase, - MeterValueUnit, - type OCPP16MeterValue, - type OCPP16SampledValue, - OCPPVersion, - RequestCommand, - type SampledValueTemplate, -} from '../../../types/index.js' -import { ACElectricUtils, DCElectricUtils, roundTo } from '../../../utils/index.js' -import { - addLineToLineVoltageToMeterValue, - addMainVoltageToMeterValue, - addPhaseVoltageToMeterValue, - buildCurrentMeasurandValue, - buildEmptyMeterValue, - buildEnergyMeasurandValue, - buildPowerMeasurandValue, - buildSampledValue, - buildSocMeasurandValue, - buildVoltageMeasurandValue, - updateConnectorEnergyValues, - validateCurrentMeasurandPhaseValue, - validateCurrentMeasurandValue, - validateEnergyMeasurandValue, - validatePowerMeasurandValue, - validateSocMeasurandValue, -} from '../OCPPServiceUtils.js' - -export const buildMeterValueForOCPP16 = ( - 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 buildSampledValueForOCPP16(sampledValueTemplate, value, context, phase) - } - // SoC measurand - const socMeasurand = buildSocMeasurandValue( - chargingStation, - connectorId, - undefined, - measurandsKey - ) - if (socMeasurand != null) { - const socSampledValue = buildVersionedSampledValue(socMeasurand.template, socMeasurand.value) - 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 - ) - for ( - let phase = 1; - chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); - phase++ - ) { - addPhaseVoltageToMeterValue( - chargingStation, - connectorId, - meterValue, - voltageMeasurand, - phase, - buildVersionedSampledValue - ) - addLineToLineVoltageToMeterValue( - chargingStation, - connectorId, - meterValue, - voltageMeasurand, - phase, - buildVersionedSampledValue - ) - } - } - // Power.Active.Import measurand - const powerMeasurand = buildPowerMeasurandValue( - chargingStation, - connectorId, - undefined, - measurandsKey - ) - if (powerMeasurand != 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) - ) - 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, undefined, 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 != 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) - ) - 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], - undefined, - 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 - ) - ) - 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. - * @param value - The measured value. - * @param context - The reading context. - * @param phase - The phase of the measurement. - * @returns The built OCPP 1.6 sampled value. - */ -export function buildSampledValueForOCPP16 ( - sampledValueTemplate: SampledValueTemplate, - value: number, - context?: MeterValueContext, - phase?: MeterValuePhase -): OCPP16SampledValue { - return buildSampledValue( - OCPPVersion.VERSION_16, - sampledValueTemplate, - value, - context, - phase - ) as OCPP16SampledValue -} diff --git a/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts b/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts index 28cf8eee..6f6894fa 100644 --- a/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts +++ b/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts @@ -1,4 +1,42 @@ -import type { ChargingStationInfo, OCPP16BootNotificationRequest } from '../../../types/index.js' +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, + OCPPVersion, + RequestCommand, + type SampledValueTemplate, +} from '../../../types/index.js' +import { ACElectricUtils, DCElectricUtils, roundTo } from '../../../utils/index.js' +import { + addLineToLineVoltageToMeterValue, + addMainVoltageToMeterValue, + addPhaseVoltageToMeterValue, + buildCurrentMeasurandValue, + buildEmptyMeterValue, + buildEnergyMeasurandValue, + buildPowerMeasurandValue, + buildSampledValue, + buildSocMeasurandValue, + buildVoltageMeasurandValue, + updateConnectorEnergyValues, + validateCurrentMeasurandPhaseValue, + validateCurrentMeasurandValue, + validateEnergyMeasurandValue, + validatePowerMeasurandValue, + validateSocMeasurandValue, +} from '../OCPPServiceUtils.js' export const buildOCPP16BootNotificationRequest = ( stationInfo: ChargingStationInfo @@ -23,3 +61,269 @@ export const buildOCPP16BootNotificationRequest = ( meterType: stationInfo.meterType, }), }) + +export const buildMeterValueForOCPP16 = ( + 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 buildSampledValueForOCPP16(sampledValueTemplate, value, context, phase) + } + // SoC measurand + const socMeasurand = buildSocMeasurandValue( + chargingStation, + connectorId, + undefined, + measurandsKey + ) + if (socMeasurand != null) { + const socSampledValue = buildVersionedSampledValue(socMeasurand.template, socMeasurand.value) + 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 + ) + for ( + let phase = 1; + chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); + phase++ + ) { + addPhaseVoltageToMeterValue( + chargingStation, + connectorId, + meterValue, + voltageMeasurand, + phase, + buildVersionedSampledValue + ) + addLineToLineVoltageToMeterValue( + chargingStation, + connectorId, + meterValue, + voltageMeasurand, + phase, + buildVersionedSampledValue + ) + } + } + // Power.Active.Import measurand + const powerMeasurand = buildPowerMeasurandValue( + chargingStation, + connectorId, + undefined, + measurandsKey + ) + if (powerMeasurand != 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) + ) + 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, undefined, 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 != 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) + ) + 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], + undefined, + 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 + ) + ) + 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. + * @param value - The measured value. + * @param context - The reading context. + * @param phase - The phase of the measurement. + * @returns The built OCPP 1.6 sampled value. + */ +export function buildSampledValueForOCPP16 ( + sampledValueTemplate: SampledValueTemplate, + value: number, + context?: MeterValueContext, + phase?: MeterValuePhase +): OCPP16SampledValue { + return buildSampledValue( + OCPPVersion.VERSION_16, + sampledValueTemplate, + value, + context, + phase + ) as OCPP16SampledValue +} diff --git a/src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts b/src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts deleted file mode 100644 index 2bfe8564..00000000 --- a/src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts +++ /dev/null @@ -1,212 +0,0 @@ -import type { ChargingStation } from '../../../charging-station/index.js' - -import { OCPPError } from '../../../exception/index.js' -import { - type ConfigurationKeyType, - ErrorType, - type MeterValueContext, - type MeterValuePhase, - MeterValueUnit, - type OCPP20MeterValue, - type OCPP20SampledValue, - OCPPVersion, - RequestCommand, - type SampledValueTemplate, -} from '../../../types/index.js' -import { roundTo } from '../../../utils/index.js' -import { - addLineToLineVoltageToMeterValue, - addMainVoltageToMeterValue, - addPhaseVoltageToMeterValue, - buildCurrentMeasurandValue, - buildEmptyMeterValue, - buildEnergyMeasurandValue, - buildPowerMeasurandValue, - buildSampledValue, - buildSocMeasurandValue, - buildVoltageMeasurandValue, - updateConnectorEnergyValues, - validateEnergyMeasurandValue, - validateSocMeasurandValue, -} from '../OCPPServiceUtils.js' - -export const buildMeterValueForOCPP20 = ( - 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 buildSampledValueForOCPP20(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 - ) - } - } - // Energy.Active.Import.Register measurand - 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 - ) - } - // Power.Active.Import measurand - const powerMeasurand = buildPowerMeasurandValue( - chargingStation, - connectorId, - evseId, - measurandsKey - ) - if (powerMeasurand?.values.allPhases != null) { - const powerSampledValue = buildVersionedSampledValue( - powerMeasurand.template, - powerMeasurand.values.allPhases, - context - ) - meterValue.sampledValue.push(powerSampledValue) - } - // Current.Import measurand - const currentMeasurand = buildCurrentMeasurandValue( - chargingStation, - connectorId, - evseId, - measurandsKey - ) - if (currentMeasurand?.values.allPhases != null) { - const currentSampledValue = buildVersionedSampledValue( - currentMeasurand.template, - currentMeasurand.values.allPhases, - context - ) - meterValue.sampledValue.push(currentSampledValue) - } - return meterValue -} - -/** - * Builds an OCPP 2.0 sampled value from a template and measurement data. - * @param sampledValueTemplate - The sampled value template to use. - * @param value - The measured value. - * @param context - The reading context. - * @param phase - The phase of the measurement. - * @returns The built OCPP 2.0 sampled value. - */ -export function buildSampledValueForOCPP20 ( - sampledValueTemplate: SampledValueTemplate, - value: number, - context?: MeterValueContext, - phase?: MeterValuePhase -): OCPP20SampledValue { - return buildSampledValue( - OCPPVersion.VERSION_20, - sampledValueTemplate, - value, - context, - phase - ) as OCPP20SampledValue -} diff --git a/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts b/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts index 83ad669b..5457e870 100644 --- a/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts +++ b/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts @@ -1,8 +1,37 @@ +import type { ChargingStation } from '../../../charging-station/index.js' + +import { OCPPError } from '../../../exception/index.js' import { BootReasonEnumType, type ChargingStationInfo, + type ConfigurationKeyType, + ErrorType, + type MeterValueContext, + type MeterValuePhase, + MeterValueUnit, type OCPP20BootNotificationRequest, + type OCPP20MeterValue, + type OCPP20SampledValue, + OCPPVersion, + RequestCommand, + type SampledValueTemplate, } from '../../../types/index.js' +import { roundTo } from '../../../utils/index.js' +import { + addLineToLineVoltageToMeterValue, + addMainVoltageToMeterValue, + addPhaseVoltageToMeterValue, + buildCurrentMeasurandValue, + buildEmptyMeterValue, + buildEnergyMeasurandValue, + buildPowerMeasurandValue, + buildSampledValue, + buildSocMeasurandValue, + buildVoltageMeasurandValue, + updateConnectorEnergyValues, + validateEnergyMeasurandValue, + validateSocMeasurandValue, +} from '../OCPPServiceUtils.js' export const buildOCPP20BootNotificationRequest = ( stationInfo: ChargingStationInfo, @@ -26,3 +55,184 @@ export const buildOCPP20BootNotificationRequest = ( }, reason: bootReason, }) + +export const buildMeterValueForOCPP20 = ( + 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 buildSampledValueForOCPP20(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 + ) + } + } + // Energy.Active.Import.Register measurand + 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 + ) + } + // Power.Active.Import measurand + const powerMeasurand = buildPowerMeasurandValue( + chargingStation, + connectorId, + evseId, + measurandsKey + ) + if (powerMeasurand?.values.allPhases != null) { + const powerSampledValue = buildVersionedSampledValue( + powerMeasurand.template, + powerMeasurand.values.allPhases, + context + ) + meterValue.sampledValue.push(powerSampledValue) + } + // Current.Import measurand + const currentMeasurand = buildCurrentMeasurandValue( + chargingStation, + connectorId, + evseId, + measurandsKey + ) + if (currentMeasurand?.values.allPhases != null) { + const currentSampledValue = buildVersionedSampledValue( + currentMeasurand.template, + currentMeasurand.values.allPhases, + context + ) + meterValue.sampledValue.push(currentSampledValue) + } + return meterValue +} + +/** + * Builds an OCPP 2.0 sampled value from a template and measurement data. + * @param sampledValueTemplate - The sampled value template to use. + * @param value - The measured value. + * @param context - The reading context. + * @param phase - The phase of the measurement. + * @returns The built OCPP 2.0 sampled value. + */ +export function buildSampledValueForOCPP20 ( + sampledValueTemplate: SampledValueTemplate, + value: number, + context?: MeterValueContext, + phase?: MeterValuePhase +): OCPP20SampledValue { + return buildSampledValue( + OCPPVersion.VERSION_20, + sampledValueTemplate, + value, + context, + phase + ) as OCPP20SampledValue +} diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index ba61ce42..e277e39f 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -57,10 +57,14 @@ import { min, roundTo, } from '../../utils/index.js' -import { buildMeterValueForOCPP16 } from './1.6/OCPP16MeterValueBuilders.js' -import { buildOCPP16BootNotificationRequest } from './1.6/OCPP16RequestBuilders.js' -import { buildMeterValueForOCPP20 } from './2.0/OCPP20MeterValueBuilders.js' -import { buildOCPP20BootNotificationRequest } from './2.0/OCPP20RequestBuilders.js' +import { + buildMeterValueForOCPP16, + buildOCPP16BootNotificationRequest, +} from './1.6/OCPP16RequestBuilders.js' +import { + buildMeterValueForOCPP20, + buildOCPP20BootNotificationRequest, +} from './2.0/OCPP20RequestBuilders.js' import { OCPPConstants } from './OCPPConstants.js' const moduleName = 'OCPPServiceUtils' -- 2.43.0