From d6ab80b1885d34dd8e195b2cd5ea16423d732070 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 1 Apr 2026 18:08:16 +0200 Subject: [PATCH] refactor(ocpp): extract version-specific builders and transaction operations Extract meter value builders into OCPP16/20MeterValueBuilders.ts leaf modules. Extract boot notification builders into OCPP16/20RequestBuilders.ts leaf modules. Move buildBootNotificationRequest dispatcher from Operations to Utils. Extract OCPP 2.0 startTransactionOnConnector and stopTransactionOnConnector into OCPP20ServiceUtils, removing inline OCPP 2.0 enums and logic from the shared Operations module. OCPPServiceUtils reduced from 2095 to 1648 lines. --- .../ocpp/1.6/OCPP16MeterValueBuilders.ts | 303 +++++++++++ .../ocpp/1.6/OCPP16RequestBuilders.ts | 25 + .../ocpp/1.6/OCPP16ServiceUtils.ts | 33 -- .../ocpp/2.0/OCPP20MeterValueBuilders.ts | 212 ++++++++ .../ocpp/2.0/OCPP20RequestBuilders.ts | 28 + .../ocpp/2.0/OCPP20ServiceUtils.ts | 104 ++-- .../ocpp/OCPPServiceOperations.ts | 89 +--- src/charging-station/ocpp/OCPPServiceUtils.ts | 500 ++---------------- src/charging-station/ocpp/index.ts | 3 +- .../ocpp/OCPPServiceOperations.test.ts | 2 +- 10 files changed, 682 insertions(+), 617 deletions(-) create mode 100644 src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts create mode 100644 src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts create mode 100644 src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts create mode 100644 src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts diff --git a/src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts b/src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts new file mode 100644 index 00000000..c51ab485 --- /dev/null +++ b/src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts @@ -0,0 +1,303 @@ +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 new file mode 100644 index 00000000..28cf8eee --- /dev/null +++ b/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts @@ -0,0 +1,25 @@ +import type { ChargingStationInfo, OCPP16BootNotificationRequest } from '../../../types/index.js' + +export const buildOCPP16BootNotificationRequest = ( + stationInfo: ChargingStationInfo +): OCPP16BootNotificationRequest => ({ + chargePointModel: stationInfo.chargePointModel, + chargePointVendor: stationInfo.chargePointVendor, + ...(stationInfo.chargeBoxSerialNumber != null && { + chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber, + }), + ...(stationInfo.chargePointSerialNumber != null && { + chargePointSerialNumber: stationInfo.chargePointSerialNumber, + }), + ...(stationInfo.firmwareVersion != null && { + firmwareVersion: stationInfo.firmwareVersion, + }), + ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }), + ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }), + ...(stationInfo.meterSerialNumber != null && { + meterSerialNumber: stationInfo.meterSerialNumber, + }), + ...(stationInfo.meterType != null && { + meterType: stationInfo.meterType, + }), +}) diff --git a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts index 9894752b..1d73ce05 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts @@ -16,14 +16,12 @@ import { import { BaseError } from '../../../exception/index.js' import { ChargePointErrorCode, - type ChargingStationInfo, type ConfigurationKey, type GenericResponse, type MeterValuesRequest, type MeterValuesResponse, OCPP16AuthorizationStatus, type OCPP16AvailabilityType, - type OCPP16BootNotificationRequest, type OCPP16ChangeAvailabilityResponse, OCPP16ChargePointStatus, type OCPP16ChargingProfile, @@ -110,37 +108,6 @@ export class OCPP16ServiceUtils { [OCPP16RequestCommand.STOP_TRANSACTION, 'StopTransaction'], ] - /** - * Builds an OCPP 1.6 BootNotification request payload from station info. - * @param stationInfo - Charging station information - * @returns Formatted OCPP 1.6 BootNotification request payload - */ - public static buildBootNotificationRequest ( - stationInfo: ChargingStationInfo - ): OCPP16BootNotificationRequest { - return { - chargePointModel: stationInfo.chargePointModel, - chargePointVendor: stationInfo.chargePointVendor, - ...(stationInfo.chargeBoxSerialNumber != null && { - chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber, - }), - ...(stationInfo.chargePointSerialNumber != null && { - chargePointSerialNumber: stationInfo.chargePointSerialNumber, - }), - ...(stationInfo.firmwareVersion != null && { - firmwareVersion: stationInfo.firmwareVersion, - }), - ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }), - ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }), - ...(stationInfo.meterSerialNumber != null && { - meterSerialNumber: stationInfo.meterSerialNumber, - }), - ...(stationInfo.meterType != null && { - meterType: stationInfo.meterType, - }), - } satisfies OCPP16BootNotificationRequest - } - /** * @param commandParams - Status notification parameters * @returns Formatted OCPP 1.6 StatusNotification request payload diff --git a/src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts b/src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts new file mode 100644 index 00000000..2bfe8564 --- /dev/null +++ b/src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts @@ -0,0 +1,212 @@ +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 new file mode 100644 index 00000000..83ad669b --- /dev/null +++ b/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts @@ -0,0 +1,28 @@ +import { + BootReasonEnumType, + type ChargingStationInfo, + type OCPP20BootNotificationRequest, +} from '../../../types/index.js' + +export const buildOCPP20BootNotificationRequest = ( + stationInfo: ChargingStationInfo, + bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp +): OCPP20BootNotificationRequest => ({ + chargingStation: { + model: stationInfo.chargePointModel, + vendorName: stationInfo.chargePointVendor, + ...(stationInfo.firmwareVersion != null && { + firmwareVersion: stationInfo.firmwareVersion, + }), + ...(stationInfo.chargeBoxSerialNumber != null && { + serialNumber: stationInfo.chargeBoxSerialNumber, + }), + ...((stationInfo.iccid != null || stationInfo.imsi != null) && { + modem: { + ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }), + ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }), + }, + }), + }, + reason: bootReason, +}) diff --git a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts index 940d1c73..88fd4b7c 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts @@ -3,16 +3,15 @@ import { secondsToMilliseconds } from 'date-fns' import { type ChargingStation, resetConnectorStatus } from '../../../charging-station/index.js' import { OCPPError } from '../../../exception/index.js' import { - BootReasonEnumType, - type ChargingStationInfo, type ConnectorStatus, ConnectorStatusEnum, ErrorType, - type OCPP20BootNotificationRequest, + OCPP20AuthorizationStatusEnumType, OCPP20ChargingStateEnumType, OCPP20ComponentName, type OCPP20ConnectorStatusEnumType, type OCPP20EVSEType, + OCPP20IdTokenEnumType, type OCPP20IdTokenInfoType, type OCPP20IdTokenType, OCPP20IncomingRequestCommand, @@ -32,6 +31,9 @@ import { OCPPVersion, ReasonCodeEnumType, RequestCommand, + type StartTransactionResult, + type StopTransactionReason, + type StopTransactionResult, type UUIDv4, } from '../../../types/index.js' import { @@ -56,6 +58,7 @@ import { sendAndSetConnectorStatus } from '../OCPPConnectorStatusOperations.js' import { buildMeterValue, createPayloadConfigs, + mapStopReasonToOCPP20, PayloadValidatorOptions, } from '../OCPPServiceUtils.js' import { OCPP20VariableManager } from './OCPP20VariableManager.js' @@ -113,37 +116,6 @@ export class OCPP20ServiceUtils { [OCPP20RequestCommand.TRANSACTION_EVENT, 'TransactionEvent'], ] - /** - * Builds an OCPP 2.0 BootNotification request payload from station info. - * @param stationInfo - Charging station information - * @param bootReason - Reason for the boot notification - * @returns Formatted OCPP 2.0 BootNotification request payload - */ - public static buildBootNotificationRequest ( - stationInfo: ChargingStationInfo, - bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp - ): OCPP20BootNotificationRequest { - return { - chargingStation: { - model: stationInfo.chargePointModel, - vendorName: stationInfo.chargePointVendor, - ...(stationInfo.firmwareVersion != null && { - firmwareVersion: stationInfo.firmwareVersion, - }), - ...(stationInfo.chargeBoxSerialNumber != null && { - serialNumber: stationInfo.chargeBoxSerialNumber, - }), - ...((stationInfo.iccid != null || stationInfo.imsi != null) && { - modem: { - ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }), - ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }), - }, - }), - }, - reason: bootReason, - } satisfies OCPP20BootNotificationRequest - } - /** * @param chargingStation - Target charging station for EVSE resolution * @param commandParams - Status notification parameters @@ -816,6 +788,43 @@ export class OCPP20ServiceUtils { ) } + public static async startTransactionOnConnector ( + chargingStation: ChargingStation, + connectorId: number, + idTag?: string + ): Promise { + const connectorStatus = chargingStation.getConnectorStatus(connectorId) + let transactionId = connectorStatus?.transactionId as string | undefined + if (transactionId == null) { + transactionId = generateUUID() + if (connectorStatus != null) { + connectorStatus.transactionId = transactionId + } + OCPP20ServiceUtils.resetTransactionSequenceNumber(chargingStation, connectorId) + } + const startedMeterValues = OCPP20ServiceUtils.buildTransactionStartedMeterValues( + chargingStation, + transactionId + ) + const response = await OCPP20ServiceUtils.sendTransactionEvent( + chargingStation, + OCPP20TransactionEventEnumType.Started, + OCPP20TriggerReasonEnumType.Authorized, + connectorId, + transactionId, + { + idToken: + idTag != null ? { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 } : undefined, + ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }), + } + ) + return { + accepted: + response.idTokenInfo == null || + response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted, + } + } + /** * Start periodic TransactionEvent(Updated) with meter values for a connector. * @param chargingStation - Target charging station @@ -986,6 +995,33 @@ export class OCPP20ServiceUtils { } } + public static async stopTransactionOnConnector ( + chargingStation: ChargingStation, + connectorId: number, + reason?: StopTransactionReason + ): Promise { + const evseId = chargingStation.getEvseIdByConnectorId(connectorId) + if (evseId == null) { + logger.warn( + `${chargingStation.logPrefix()} stopTransactionOnConnector: cannot resolve EVSE ID for connector ${connectorId.toString()}, skipping` + ) + return { accepted: false } + } + const { stoppedReason, triggerReason } = mapStopReasonToOCPP20(reason) + const response = await OCPP20ServiceUtils.requestStopTransaction( + chargingStation, + connectorId, + evseId, + triggerReason, + stoppedReason + ) + return { + accepted: + response.idTokenInfo == null || + response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted, + } + } + /** * Stop periodic TransactionEvent(Updated) sending for a connector. * @param chargingStation - Target charging station diff --git a/src/charging-station/ocpp/OCPPServiceOperations.ts b/src/charging-station/ocpp/OCPPServiceOperations.ts index 0607932c..88495b1c 100644 --- a/src/charging-station/ocpp/OCPPServiceOperations.ts +++ b/src/charging-station/ocpp/OCPPServiceOperations.ts @@ -1,21 +1,15 @@ -import type { BootReasonEnumType, StopTransactionReason } from '../../types/index.js' +import type { StopTransactionReason } from '../../types/index.js' import { type ChargingStation } from '../../charging-station/index.js' import { OCPPError } from '../../exception/index.js' import { AuthorizationStatus, - type BootNotificationRequest, - type ChargingStationInfo, ErrorType, - OCPP20AuthorizationStatusEnumType, - OCPP20IdTokenEnumType, - OCPP20TransactionEventEnumType, - OCPP20TriggerReasonEnumType, OCPPVersion, type StartTransactionResult, type StopTransactionResult, } from '../../types/index.js' -import { generateUUID, logger, truncateId } from '../../utils/index.js' +import { logger, truncateId } from '../../utils/index.js' import { OCPP16ServiceUtils } from './1.6/OCPP16ServiceUtils.js' import { OCPP20ServiceUtils } from './2.0/OCPP20ServiceUtils.js' import { @@ -49,38 +43,8 @@ export const startTransactionOnConnector = async ( return { accepted: response.idTagInfo.status === AuthorizationStatus.ACCEPTED } } case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: { - const connectorStatus = chargingStation.getConnectorStatus(connectorId) - let transactionId = connectorStatus?.transactionId as string | undefined - if (transactionId == null) { - transactionId = generateUUID() - if (connectorStatus != null) { - connectorStatus.transactionId = transactionId - } - OCPP20ServiceUtils.resetTransactionSequenceNumber(chargingStation, connectorId) - } - const startedMeterValues = OCPP20ServiceUtils.buildTransactionStartedMeterValues( - chargingStation, - transactionId - ) - const response = await OCPP20ServiceUtils.sendTransactionEvent( - chargingStation, - OCPP20TransactionEventEnumType.Started, - OCPP20TriggerReasonEnumType.Authorized, - connectorId, - transactionId, - { - idToken: - idTag != null ? { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 } : undefined, - ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }), - } - ) - return { - accepted: - response.idTokenInfo == null || - response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted, - } - } + case OCPPVersion.VERSION_201: + return OCPP20ServiceUtils.startTransactionOnConnector(chargingStation, connectorId, idTag) default: throw new OCPPError( ErrorType.INTERNAL_ERROR, @@ -112,28 +76,8 @@ export const stopTransactionOnConnector = async ( return { accepted: response.idTagInfo?.status === AuthorizationStatus.ACCEPTED } } case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: { - const evseId = chargingStation.getEvseIdByConnectorId(connectorId) - if (evseId == null) { - logger.warn( - `${chargingStation.logPrefix()} stopTransactionOnConnector: cannot resolve EVSE ID for connector ${connectorId.toString()}, skipping` - ) - return { accepted: false } - } - const { stoppedReason, triggerReason } = mapStopReasonToOCPP20(reason) - const response = await OCPP20ServiceUtils.requestStopTransaction( - chargingStation, - connectorId, - evseId, - triggerReason, - stoppedReason - ) - return { - accepted: - response.idTokenInfo == null || - response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted, - } - } + case OCPPVersion.VERSION_201: + return OCPP20ServiceUtils.stopTransactionOnConnector(chargingStation, connectorId, reason) default: throw new OCPPError( ErrorType.INTERNAL_ERROR, @@ -254,27 +198,6 @@ export const flushQueuedTransactionMessages = async ( } } -/** - * Builds an OCPP BootNotification request using the appropriate version-specific handler. - * @param stationInfo - Charging station information - * @param bootReason - Optional boot reason (OCPP 2.0 only) - * @returns The BootNotification request payload, or undefined if the OCPP version is unsupported - */ -export const buildBootNotificationRequest = ( - stationInfo: ChargingStationInfo, - bootReason?: BootReasonEnumType -): BootNotificationRequest | undefined => { - switch (stationInfo.ocppVersion) { - case OCPPVersion.VERSION_16: - return OCPP16ServiceUtils.buildBootNotificationRequest(stationInfo) - case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: - return OCPP20ServiceUtils.buildBootNotificationRequest(stationInfo, bootReason) - default: - return undefined - } -} - export const isIdTagAuthorized = async ( chargingStation: ChargingStation, connectorId: number, diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index 495e8445..ba61ce42 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -6,11 +6,13 @@ import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' -import type { StopTransactionReason } from '../../types/index.js' +import type { BootReasonEnumType, StopTransactionReason } from '../../types/index.js' import { type ChargingStation, getConfigurationKey } from '../../charging-station/index.js' import { BaseError, OCPPError } from '../../exception/index.js' import { + type BootNotificationRequest, + type ChargingStationInfo, type ConfigurationKeyType, type ConnectorStatus, CurrentType, @@ -27,10 +29,8 @@ import { MeterValueMeasurand, MeterValuePhase, MeterValueUnit, - type OCPP16MeterValue, type OCPP16SampledValue, OCPP16StopTransactionReason, - type OCPP20MeterValue, OCPP20ReasonEnumType, type OCPP20SampledValue, OCPP20TriggerReasonEnumType, @@ -57,6 +57,10 @@ 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 { OCPPConstants } from './OCPPConstants.js' const moduleName = 'OCPPServiceUtils' @@ -86,6 +90,21 @@ interface SingleValueMeasurandData { value: number } +export const buildBootNotificationRequest = ( + stationInfo: ChargingStationInfo, + bootReason?: BootReasonEnumType +): BootNotificationRequest | undefined => { + switch (stationInfo.ocppVersion) { + case OCPPVersion.VERSION_16: + return buildOCPP16BootNotificationRequest(stationInfo) + case OCPPVersion.VERSION_20: + case OCPPVersion.VERSION_201: + return buildOCPP20BootNotificationRequest(stationInfo, bootReason) + default: + return undefined + } +} + /** * Maps an OCPP 1.6 or generic stop transaction reason to OCPP 2.0 stopped and trigger reasons. * @param reason - Stop transaction reason to map @@ -215,7 +234,7 @@ export const convertDateToISOString = (object: T): void => { } } -const buildSocMeasurandValue = ( +export const buildSocMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, evseId?: number, @@ -247,7 +266,7 @@ const buildSocMeasurandValue = ( } } -const validateSocMeasurandValue = ( +export const validateSocMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, sampledValue: SampledValue, @@ -270,7 +289,7 @@ const validateSocMeasurandValue = ( } } -const buildVoltageMeasurandValue = ( +export const buildVoltageMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, evseId?: number, @@ -303,7 +322,7 @@ const buildVoltageMeasurandValue = ( } } -const addMainVoltageToMeterValue = ( +export const addMainVoltageToMeterValue = ( chargingStation: ChargingStation, meterValue: { sampledValue: TSampledValue[] }, voltageData: { template: SampledValueTemplate; value: number }, @@ -329,7 +348,7 @@ const addMainVoltageToMeterValue = ( } } -const addPhaseVoltageToMeterValue = ( +export const addPhaseVoltageToMeterValue = ( chargingStation: ChargingStation, connectorId: number, meterValue: { sampledValue: TSampledValue[] }, @@ -382,7 +401,7 @@ const addPhaseVoltageToMeterValue = ( ) } -const addLineToLineVoltageToMeterValue = ( +export const addLineToLineVoltageToMeterValue = ( chargingStation: ChargingStation, connectorId: number, meterValue: { sampledValue: TSampledValue[] }, @@ -443,7 +462,7 @@ const addLineToLineVoltageToMeterValue = ( ) } -const buildEnergyMeasurandValue = ( +export const buildEnergyMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, interval: number, @@ -493,7 +512,7 @@ const buildEnergyMeasurandValue = ( } } -const updateConnectorEnergyValues = ( +export const updateConnectorEnergyValues = ( connectorStatus: ConnectorStatus | undefined, energyValue: number ): void => { @@ -513,7 +532,7 @@ const updateConnectorEnergyValues = ( } } -const validateEnergyMeasurandValue = ( +export const validateEnergyMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, sampledValue: SampledValue, @@ -534,7 +553,7 @@ const validateEnergyMeasurandValue = ( } } -const buildPowerMeasurandValue = ( +export const buildPowerMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, evseId?: number, @@ -742,7 +761,7 @@ const buildPowerMeasurandValue = ( } } -const validatePowerMeasurandValue = ( +export const validatePowerMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, connectorStatus: ConnectorStatus | undefined, @@ -765,7 +784,7 @@ const validatePowerMeasurandValue = ( } } -const validateCurrentMeasurandValue = ( +export const validateCurrentMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, connectorStatus: ConnectorStatus | undefined, @@ -788,7 +807,7 @@ const validateCurrentMeasurandValue = ( } } -const validateCurrentMeasurandPhaseValue = ( +export const validateCurrentMeasurandPhaseValue = ( chargingStation: ChargingStation, connectorId: number, connectorStatus: ConnectorStatus | undefined, @@ -814,7 +833,7 @@ const validateCurrentMeasurandPhaseValue = ( } } -const buildCurrentMeasurandValue = ( +export const buildCurrentMeasurandValue = ( chargingStation: ChargingStation, connectorId: number, evseId?: number, @@ -1073,407 +1092,6 @@ export const buildMeterValue = ( } } -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 -} - -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 -} - const checkMeasurandPowerDivider = ( chargingStation: ChargingStation, measurandType: MeterValueMeasurand | undefined @@ -1688,52 +1306,6 @@ export function buildSampledValue ( } } -/** - * 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. - */ -function buildSampledValueForOCPP16 ( - sampledValueTemplate: SampledValueTemplate, - value: number, - context?: MeterValueContext, - phase?: MeterValuePhase -): OCPP16SampledValue { - return buildSampledValue( - OCPPVersion.VERSION_16, - sampledValueTemplate, - value, - context, - phase - ) as OCPP16SampledValue -} - -/** - * 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. - */ -function buildSampledValueForOCPP20 ( - sampledValueTemplate: SampledValueTemplate, - value: number, - context?: MeterValueContext, - phase?: MeterValuePhase -): OCPP20SampledValue { - return buildSampledValue( - OCPPVersion.VERSION_20, - sampledValueTemplate, - value, - context, - phase - ) as OCPP20SampledValue -} - const getMeasurandDefaultContext = (measurandType: MeterValueMeasurand): MeterValueContext => { return MeterValueContext.SAMPLE_PERIODIC } diff --git a/src/charging-station/ocpp/index.ts b/src/charging-station/ocpp/index.ts index d570f06e..b2997cdc 100644 --- a/src/charging-station/ocpp/index.ts +++ b/src/charging-station/ocpp/index.ts @@ -17,11 +17,10 @@ export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js' export { OCPPRequestService } from './OCPPRequestService.js' export { createOCPPServices } from './OCPPServiceFactory.js' export { - buildBootNotificationRequest, flushQueuedTransactionMessages, isIdTagAuthorized, startTransactionOnConnector, stopRunningTransactions, stopTransactionOnConnector, } from './OCPPServiceOperations.js' -export { buildMeterValue } from './OCPPServiceUtils.js' +export { buildBootNotificationRequest, buildMeterValue } from './OCPPServiceUtils.js' diff --git a/tests/charging-station/ocpp/OCPPServiceOperations.test.ts b/tests/charging-station/ocpp/OCPPServiceOperations.test.ts index f83a43bd..ccbd486e 100644 --- a/tests/charging-station/ocpp/OCPPServiceOperations.test.ts +++ b/tests/charging-station/ocpp/OCPPServiceOperations.test.ts @@ -13,12 +13,12 @@ import type { ChargingStationInfo } from '../../../src/types/index.js' import type { MockChargingStationOptions } from '../helpers/StationHelpers.js' import { - buildBootNotificationRequest, flushQueuedTransactionMessages, startTransactionOnConnector, stopRunningTransactions, stopTransactionOnConnector, } from '../../../src/charging-station/ocpp/OCPPServiceOperations.js' +import { buildBootNotificationRequest } from '../../../src/charging-station/ocpp/OCPPServiceUtils.js' import { BootReasonEnumType, type OCPP20TransactionEventRequest, -- 2.53.0