From 41f3983a4f934199769f9ef1c46bfae2adc22b56 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 5 Dec 2023 14:25:21 +0100 Subject: [PATCH] perf: minimize OCPPUtils exports MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- package.json | 2 +- pnpm-lock.yaml | 32 +- src/charging-station/ChargingStation.ts | 9 +- .../ChargingStationWorkerBroadcastChannel.ts | 5 +- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 10 +- .../ocpp/1.6/OCPP16ResponseService.ts | 4 +- .../ocpp/1.6/OCPP16ServiceUtils.ts | 898 +------------- src/charging-station/ocpp/OCPPServiceUtils.ts | 1098 +++++++++++++++-- src/charging-station/ocpp/index.ts | 4 +- src/types/index.ts | 14 +- src/types/ocpp/1.6/MeterValues.ts | 16 +- src/types/ocpp/1.6/Requests.ts | 2 +- src/types/ocpp/1.6/Responses.ts | 2 +- src/types/ocpp/MeterValues.ts | 18 + ui/web/pnpm-lock.yaml | 6 +- 15 files changed, 1073 insertions(+), 1047 deletions(-) diff --git a/package.json b/package.json index 68c7be88..a9833b59 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "http-status-codes": "^2.3.0", "just-merge": "^3.2.0", "logform": "^2.6.0", - "mnemonist": "^0.39.5", + "mnemonist": "^0.39.6", "mongodb": "^6.3.0", "poolifier": "^3.0.9", "tar": "^6.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcddaeeb..ac7b4fba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ dependencies: specifier: ^2.6.0 version: 2.6.0 mnemonist: - specifier: ^0.39.5 - version: 0.39.5 + specifier: ^0.39.6 + version: 0.39.6 mongodb: specifier: ^6.3.0 version: 6.3.0 @@ -1435,7 +1435,7 @@ packages: '@octokit/graphql': 7.0.2 '@octokit/request': 8.1.6 '@octokit/request-error': 5.0.1 - '@octokit/types': 12.3.0 + '@octokit/types': 12.4.0 before-after-hook: 2.2.3 universal-user-agent: 6.0.1 dev: true @@ -1444,7 +1444,7 @@ packages: resolution: {integrity: sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==} engines: {node: '>= 18'} dependencies: - '@octokit/types': 12.3.0 + '@octokit/types': 12.4.0 universal-user-agent: 6.0.1 dev: true @@ -1453,7 +1453,7 @@ packages: engines: {node: '>= 18'} dependencies: '@octokit/request': 8.1.6 - '@octokit/types': 12.3.0 + '@octokit/types': 12.4.0 universal-user-agent: 6.0.1 dev: true @@ -1461,14 +1461,14 @@ packages: resolution: {integrity: sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==} dev: true - /@octokit/plugin-paginate-rest@9.1.4(@octokit/core@5.0.2): - resolution: {integrity: sha512-MvZx4WvfhBnt7PtH5XE7HORsO7bBk4er1FgRIUr1qJ89NR2I6bWjGyKsxk8z42FPQ34hFQm0Baanh4gzdZR4gQ==} + /@octokit/plugin-paginate-rest@9.1.5(@octokit/core@5.0.2): + resolution: {integrity: sha512-WKTQXxK+bu49qzwv4qKbMMRXej1DU2gq017euWyKVudA6MldaSSQuxtz+vGbhxV4CjxpUxjZu6rM2wfc1FiWVg==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=5' dependencies: '@octokit/core': 5.0.2 - '@octokit/types': 12.3.0 + '@octokit/types': 12.4.0 dev: true /@octokit/plugin-request-log@4.0.0(@octokit/core@5.0.2): @@ -1487,14 +1487,14 @@ packages: '@octokit/core': '>=5' dependencies: '@octokit/core': 5.0.2 - '@octokit/types': 12.3.0 + '@octokit/types': 12.4.0 dev: true /@octokit/request-error@5.0.1: resolution: {integrity: sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==} engines: {node: '>= 18'} dependencies: - '@octokit/types': 12.3.0 + '@octokit/types': 12.4.0 deprecation: 2.3.1 once: 1.4.0 dev: true @@ -1505,7 +1505,7 @@ packages: dependencies: '@octokit/endpoint': 9.0.4 '@octokit/request-error': 5.0.1 - '@octokit/types': 12.3.0 + '@octokit/types': 12.4.0 universal-user-agent: 6.0.1 dev: true @@ -1514,13 +1514,13 @@ packages: engines: {node: '>= 18'} dependencies: '@octokit/core': 5.0.2 - '@octokit/plugin-paginate-rest': 9.1.4(@octokit/core@5.0.2) + '@octokit/plugin-paginate-rest': 9.1.5(@octokit/core@5.0.2) '@octokit/plugin-request-log': 4.0.0(@octokit/core@5.0.2) '@octokit/plugin-rest-endpoint-methods': 10.2.0(@octokit/core@5.0.2) dev: true - /@octokit/types@12.3.0: - resolution: {integrity: sha512-nJ8X2HRr234q3w/FcovDlA+ttUU4m1eJAourvfUUtwAWeqL8AsyRqfnLvVnYn3NFbUnsmzQCzLNdFerPwdmcDQ==} + /@octokit/types@12.4.0: + resolution: {integrity: sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==} dependencies: '@octokit/openapi-types': 19.1.0 dev: true @@ -6941,8 +6941,8 @@ packages: resolution: {integrity: sha512-VoAYUqmPRmzKbbqRejjqceGFp3VF81Qe8XXFGU0UXLxB7Mf4GGvyGq5Qn3k4AiQgDEV6WzobqlPOd+j0+m6IrA==} dev: true - /mnemonist@0.39.5: - resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==} + /mnemonist@0.39.6: + resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==} dependencies: obliterator: 2.0.4 dev: false diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 5860a654..cf574f55 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -49,13 +49,14 @@ import { OCPP16IncomingRequestService, OCPP16RequestService, OCPP16ResponseService, - OCPP16ServiceUtils, OCPP20IncomingRequestService, OCPP20RequestService, OCPP20ResponseService, type OCPPIncomingRequestService, type OCPPRequestService, + buildMeterValue, buildStatusNotificationRequest, + buildTransactionEndMeterValue, getMessageTypeString, sendAndSetConnectorStatus, } from './ocpp'; @@ -573,8 +574,7 @@ export class ChargingStation extends EventEmitter { } if (interval > 0) { this.getConnectorStatus(connectorId)!.transactionSetInterval = setInterval(() => { - // FIXME: Implement OCPP version agnostic helpers - const meterValue: MeterValue = OCPP16ServiceUtils.buildMeterValue( + const meterValue: MeterValue = buildMeterValue( this, connectorId, this.getConnectorStatus(connectorId)!.transactionId!, @@ -850,8 +850,7 @@ export class ChargingStation extends EventEmitter { this.stationInfo?.ocppStrictCompliance === true && this.stationInfo?.outOfOrderEndMeterValues === false ) { - // FIXME: Implement OCPP version agnostic helpers - const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue( + const transactionEndMeterValue = buildTransactionEndMeterValue( this, connectorId, this.getEnergyActiveImportRegisterByTransactionId(transactionId!), diff --git a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts index a8d9b045..2697397a 100644 --- a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts @@ -40,7 +40,7 @@ import { import { Constants, convertToInt, isEmptyObject, isNullOrUndefined, logger } from '../../utils'; import type { ChargingStation } from '../ChargingStation'; import { getConfigurationKey } from '../ConfigurationKeyUtils'; -import { OCPP16ServiceUtils } from '../ocpp'; +import { buildMeterValue } from '../ocpp'; const moduleName = 'ChargingStationWorkerBroadcastChannel'; @@ -188,8 +188,7 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne RequestCommand.METER_VALUES, { meterValue: [ - // FIXME: Implement OCPP version agnostic helpers - OCPP16ServiceUtils.buildMeterValue( + buildMeterValue( this.chargingStation, requestPayload!.connectorId!, this.chargingStation.getConnectorStatus(requestPayload!.connectorId!)! diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 21a6e1ec..8faf05cf 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -32,8 +32,6 @@ import { OCPPError } from '../../../exception'; import { type ChangeConfigurationRequest, type ChangeConfigurationResponse, - type ClearChargingProfileRequest, - type ClearChargingProfileResponse, ErrorType, type GenericResponse, GenericStatus, @@ -56,6 +54,8 @@ import { OCPP16ChargingProfilePurposeType, type OCPP16ChargingSchedule, type OCPP16ClearCacheRequest, + type OCPP16ClearChargingProfileRequest, + type OCPP16ClearChargingProfileResponse, type OCPP16DataTransferRequest, type OCPP16DataTransferResponse, OCPP16DataTransferVendorId, @@ -260,7 +260,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ], [ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, - OCPP16ServiceUtils.parseJsonSchemaFile( + OCPP16ServiceUtils.parseJsonSchemaFile( 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json', moduleName, 'constructor', @@ -798,8 +798,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { private handleRequestClearChargingProfile( chargingStation: ChargingStation, - commandPayload: ClearChargingProfileRequest, - ): ClearChargingProfileResponse { + commandPayload: OCPP16ClearChargingProfileRequest, + ): OCPP16ClearChargingProfileResponse { if ( OCPP16ServiceUtils.checkFeatureProfile( chargingStation, diff --git a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts index 14677814..7c331b6c 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts @@ -14,7 +14,6 @@ import { import { OCPPError } from '../../../exception'; import { type ChangeConfigurationResponse, - type ClearChargingProfileResponse, ErrorType, type GenericResponse, type GetConfigurationResponse, @@ -26,6 +25,7 @@ import { type OCPP16BootNotificationResponse, type OCPP16ChangeAvailabilityResponse, OCPP16ChargePointStatus, + type OCPP16ClearChargingProfileResponse, type OCPP16DataTransferResponse, type OCPP16DiagnosticsStatusNotificationResponse, type OCPP16FirmwareStatusNotificationResponse, @@ -249,7 +249,7 @@ export class OCPP16ResponseService extends OCPPResponseService { ], [ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, - OCPP16ServiceUtils.parseJsonSchemaFile( + OCPP16ServiceUtils.parseJsonSchemaFile( 'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json', moduleName, 'constructor', diff --git a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts index b194e08e..4062bc0a 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts @@ -17,52 +17,27 @@ import { hasFeatureProfile, hasReservationExpired, } from '../../../charging-station'; -import { OCPPError } from '../../../exception'; import { - type ClearChargingProfileRequest, - CurrentType, - ErrorType, type GenericResponse, type JsonType, - type MeasurandPerPhaseSampledValueTemplates, - type MeasurandValues, - MeterValueContext, - MeterValueLocation, - MeterValueUnit, OCPP16AuthorizationStatus, OCPP16AvailabilityType, type OCPP16ChangeAvailabilityResponse, OCPP16ChargePointStatus, type OCPP16ChargingProfile, type OCPP16ChargingSchedule, + type OCPP16ClearChargingProfileRequest, type OCPP16IncomingRequestCommand, type OCPP16MeterValue, - OCPP16MeterValueMeasurand, - OCPP16MeterValuePhase, + OCPP16MeterValueContext, + OCPP16MeterValueUnit, OCPP16RequestCommand, - type OCPP16SampledValue, OCPP16StandardParametersKey, OCPP16StopTransactionReason, type OCPP16SupportedFeatureProfiles, OCPPVersion, - type SampledValueTemplate, } from '../../../types'; -import { - ACElectricUtils, - Constants, - DCElectricUtils, - convertToFloat, - convertToInt, - getRandomFloatFluctuatedRounded, - getRandomFloatRounded, - getRandomInteger, - isNotEmptyArray, - isNotEmptyString, - isNullOrUndefined, - isUndefined, - logger, - roundTo, -} from '../../../utils'; +import { isNotEmptyArray, isNullOrUndefined, logger, roundTo } from '../../../utils'; import { OCPPServiceUtils } from '../OCPPServiceUtils'; export class OCPP16ServiceUtils extends OCPPServiceUtils { @@ -82,764 +57,6 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { return true; } - public static buildMeterValue( - chargingStation: ChargingStation, - connectorId: number, - transactionId: number, - interval: number, - debug = false, - ): OCPP16MeterValue { - const meterValue: OCPP16MeterValue = { - timestamp: new Date(), - sampledValue: [], - }; - const connector = chargingStation.getConnectorStatus(connectorId); - // SoC measurand - const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.STATE_OF_CHARGE, - ); - if (socSampledValueTemplate) { - const socMaximumValue = 100; - const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0; - const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value) - ? getRandomFloatFluctuatedRounded( - parseInt(socSampledValueTemplate.value), - socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : getRandomInteger(socMaximumValue, socMinimumValue); - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue), - ); - const sampledValuesIndex = meterValue.sampledValue.length - 1; - if ( - convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue || - convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue || - debug - ) { - logger.error( - `${chargingStation.logPrefix()} MeterValues measurand ${ - meterValue.sampledValue[sampledValuesIndex].measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${ - meterValue.sampledValue[sampledValuesIndex].value - }/${socMaximumValue}`, - ); - } - } - // Voltage measurand - const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.VOLTAGE, - ); - if (voltageSampledValueTemplate) { - const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value) - ? parseInt(voltageSampledValueTemplate.value) - : chargingStation.stationInfo.voltageOut!; - const fluctuationPercent = - voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; - const voltageMeasurandValue = getRandomFloatFluctuatedRounded( - voltageSampledValueTemplateValue, - fluctuationPercent, - ); - if ( - chargingStation.getNumberOfPhases() !== 3 || - (chargingStation.getNumberOfPhases() === 3 && - chargingStation.stationInfo?.mainVoltageMeterValues) - ) { - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue), - ); - } - for ( - let phase = 1; - chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); - phase++ - ) { - const phaseLineToNeutralValue = `L${phase}-N`; - const voltagePhaseLineToNeutralSampledValueTemplate = - OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.VOLTAGE, - phaseLineToNeutralValue as OCPP16MeterValuePhase, - ); - let voltagePhaseLineToNeutralMeasurandValue: number | undefined; - if (voltagePhaseLineToNeutralSampledValueTemplate) { - const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString( - voltagePhaseLineToNeutralSampledValueTemplate.value, - ) - ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) - : chargingStation.stationInfo.voltageOut!; - const fluctuationPhaseToNeutralPercent = - voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT; - voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded( - voltagePhaseLineToNeutralSampledValueTemplateValue, - fluctuationPhaseToNeutralPercent, - ); - } - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue( - voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate, - voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue, - undefined, - phaseLineToNeutralValue as OCPP16MeterValuePhase, - ), - ); - if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) { - const phaseLineToLineValue = `L${phase}-L${ - (phase + 1) % chargingStation.getNumberOfPhases() !== 0 - ? (phase + 1) % chargingStation.getNumberOfPhases() - : chargingStation.getNumberOfPhases() - }`; - const voltagePhaseLineToLineValueRounded = roundTo( - Math.sqrt(chargingStation.getNumberOfPhases()) * - chargingStation.stationInfo.voltageOut!, - 2, - ); - const voltagePhaseLineToLineSampledValueTemplate = - OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.VOLTAGE, - phaseLineToLineValue as OCPP16MeterValuePhase, - ); - let voltagePhaseLineToLineMeasurandValue: number | undefined; - if (voltagePhaseLineToLineSampledValueTemplate) { - const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString( - voltagePhaseLineToLineSampledValueTemplate.value, - ) - ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) - : voltagePhaseLineToLineValueRounded; - const fluctuationPhaseLineToLinePercent = - voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT; - voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( - voltagePhaseLineToLineSampledValueTemplateValue, - fluctuationPhaseLineToLinePercent, - ); - } - const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( - voltagePhaseLineToLineValueRounded, - fluctuationPercent, - ); - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue( - voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate, - voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue, - undefined, - phaseLineToLineValue as OCPP16MeterValuePhase, - ), - ); - } - } - } - // Power.Active.Import measurand - const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, - ); - let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; - if (chargingStation.getNumberOfPhases() === 3) { - powerPerPhaseSampledValueTemplates = { - L1: OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, - OCPP16MeterValuePhase.L1_N, - ), - L2: OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, - OCPP16MeterValuePhase.L2_N, - ), - L3: OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, - OCPP16MeterValuePhase.L3_N, - ), - }; - } - if (powerSampledValueTemplate) { - OCPP16ServiceUtils.checkMeasurandPowerDivider( - chargingStation, - powerSampledValueTemplate.measurand!, - ); - const errMsg = `MeterValues measurand ${ - powerSampledValueTemplate.measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ - chargingStation.templateFile - }, cannot calculate ${ - powerSampledValueTemplate.measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - } measurand value`; - const powerMeasurandValues: MeasurandValues = {} as MeasurandValues; - const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId); - const connectorMaximumPower = Math.round(connectorMaximumAvailablePower); - const connectorMaximumPowerPerPhase = Math.round( - connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(), - ); - const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue ?? 0); - const connectorMinimumPowerPerPhase = Math.round( - connectorMinimumPower / chargingStation.getNumberOfPhases(), - ); - switch (chargingStation.stationInfo?.currentOutType) { - case CurrentType.AC: - if (chargingStation.getNumberOfPhases() === 3) { - const defaultFluctuatedPowerPerPhase = isNotEmptyString(powerSampledValueTemplate.value) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - powerSampledValueTemplate.value, - connectorMaximumPower / unitDivider, - connectorMinimumPower / unitDivider, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumPower / unitDivider, - }, - ) / chargingStation.getNumberOfPhases(), - powerSampledValueTemplate.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : undefined; - const phase1FluctuatedValue = isNotEmptyString( - powerPerPhaseSampledValueTemplates.L1?.value, - ) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - powerPerPhaseSampledValueTemplates.L1?.value, - connectorMaximumPowerPerPhase / unitDivider, - connectorMinimumPowerPerPhase / unitDivider, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumPowerPerPhase / unitDivider, - }, - ), - powerPerPhaseSampledValueTemplates.L1?.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : undefined; - const phase2FluctuatedValue = isNotEmptyString( - powerPerPhaseSampledValueTemplates.L2?.value, - ) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - powerPerPhaseSampledValueTemplates.L2?.value, - connectorMaximumPowerPerPhase / unitDivider, - connectorMinimumPowerPerPhase / unitDivider, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumPowerPerPhase / unitDivider, - }, - ), - powerPerPhaseSampledValueTemplates.L2?.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : undefined; - const phase3FluctuatedValue = isNotEmptyString( - powerPerPhaseSampledValueTemplates.L3?.value, - ) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - powerPerPhaseSampledValueTemplates.L3?.value, - connectorMaximumPowerPerPhase / unitDivider, - connectorMinimumPowerPerPhase / unitDivider, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumPowerPerPhase / unitDivider, - }, - ), - powerPerPhaseSampledValueTemplates.L3?.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : undefined; - powerMeasurandValues.L1 = - phase1FluctuatedValue ?? - defaultFluctuatedPowerPerPhase ?? - getRandomFloatRounded( - connectorMaximumPowerPerPhase / unitDivider, - connectorMinimumPowerPerPhase / unitDivider, - ); - powerMeasurandValues.L2 = - phase2FluctuatedValue ?? - defaultFluctuatedPowerPerPhase ?? - getRandomFloatRounded( - connectorMaximumPowerPerPhase / unitDivider, - connectorMinimumPowerPerPhase / unitDivider, - ); - powerMeasurandValues.L3 = - phase3FluctuatedValue ?? - defaultFluctuatedPowerPerPhase ?? - getRandomFloatRounded( - connectorMaximumPowerPerPhase / unitDivider, - connectorMinimumPowerPerPhase / unitDivider, - ); - } else { - powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - powerSampledValueTemplate.value, - connectorMaximumPower / unitDivider, - connectorMinimumPower / unitDivider, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumPower / unitDivider, - }, - ), - powerSampledValueTemplate.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : getRandomFloatRounded( - connectorMaximumPower / unitDivider, - connectorMinimumPower / unitDivider, - ); - powerMeasurandValues.L2 = 0; - powerMeasurandValues.L3 = 0; - } - powerMeasurandValues.allPhases = roundTo( - powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, - 2, - ); - break; - case CurrentType.DC: - powerMeasurandValues.allPhases = isNotEmptyString(powerSampledValueTemplate.value) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - powerSampledValueTemplate.value, - connectorMaximumPower / unitDivider, - connectorMinimumPower / unitDivider, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumPower / unitDivider, - }, - ), - powerSampledValueTemplate.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : getRandomFloatRounded( - connectorMaximumPower / unitDivider, - connectorMinimumPower / unitDivider, - ); - break; - default: - logger.error(`${chargingStation.logPrefix()} ${errMsg}`); - throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); - } - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue( - powerSampledValueTemplate, - powerMeasurandValues.allPhases, - ), - ); - const sampledValuesIndex = meterValue.sampledValue.length - 1; - const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2); - const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2); - if ( - convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > - connectorMaximumPowerRounded || - convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < - connectorMinimumPowerRounded || - debug - ) { - logger.error( - `${chargingStation.logPrefix()} MeterValues measurand ${ - meterValue.sampledValue[sampledValuesIndex].measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${ - meterValue.sampledValue[sampledValuesIndex].value - }/${connectorMaximumPowerRounded}`, - ); - } - for ( - let phase = 1; - chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); - phase++ - ) { - const phaseValue = `L${phase}-N`; - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue( - powerPerPhaseSampledValueTemplates[ - `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates - ] ?? powerSampledValueTemplate, - powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates], - undefined, - phaseValue as OCPP16MeterValuePhase, - ), - ); - const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; - const connectorMaximumPowerPerPhaseRounded = roundTo( - connectorMaximumPowerPerPhase / unitDivider, - 2, - ); - const connectorMinimumPowerPerPhaseRounded = roundTo( - connectorMinimumPowerPerPhase / unitDivider, - 2, - ); - if ( - convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > - connectorMaximumPowerPerPhaseRounded || - convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < - connectorMinimumPowerPerPhaseRounded || - debug - ) { - logger.error( - `${chargingStation.logPrefix()} MeterValues measurand ${ - meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: phase ${ - meterValue.sampledValue[sampledValuesPerPhaseIndex].phase - }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${ - meterValue.sampledValue[sampledValuesPerPhaseIndex].value - }/${connectorMaximumPowerPerPhaseRounded}`, - ); - } - } - } - // Current.Import measurand - const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.CURRENT_IMPORT, - ); - let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; - if (chargingStation.getNumberOfPhases() === 3) { - currentPerPhaseSampledValueTemplates = { - L1: OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.CURRENT_IMPORT, - OCPP16MeterValuePhase.L1, - ), - L2: OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.CURRENT_IMPORT, - OCPP16MeterValuePhase.L2, - ), - L3: OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - OCPP16MeterValueMeasurand.CURRENT_IMPORT, - OCPP16MeterValuePhase.L3, - ), - }; - } - if (currentSampledValueTemplate) { - OCPP16ServiceUtils.checkMeasurandPowerDivider( - chargingStation, - currentSampledValueTemplate.measurand!, - ); - const errMsg = `MeterValues measurand ${ - currentSampledValueTemplate.measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ - chargingStation.templateFile - }, cannot calculate ${ - currentSampledValueTemplate.measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - } measurand value`; - const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId); - const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0; - let connectorMaximumAmperage: number; - switch (chargingStation.stationInfo?.currentOutType) { - case CurrentType.AC: - connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( - chargingStation.getNumberOfPhases(), - connectorMaximumAvailablePower, - chargingStation.stationInfo.voltageOut!, - ); - if (chargingStation.getNumberOfPhases() === 3) { - const defaultFluctuatedAmperagePerPhase = isNotEmptyString( - currentSampledValueTemplate.value, - ) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - currentSampledValueTemplate.value, - connectorMaximumAmperage, - connectorMinimumAmperage, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumAmperage, - }, - ), - currentSampledValueTemplate.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : undefined; - const phase1FluctuatedValue = isNotEmptyString( - currentPerPhaseSampledValueTemplates.L1?.value, - ) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - currentPerPhaseSampledValueTemplates.L1?.value, - connectorMaximumAmperage, - connectorMinimumAmperage, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumAmperage, - }, - ), - currentPerPhaseSampledValueTemplates.L1?.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : undefined; - const phase2FluctuatedValue = isNotEmptyString( - currentPerPhaseSampledValueTemplates.L2?.value, - ) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - currentPerPhaseSampledValueTemplates.L2?.value, - connectorMaximumAmperage, - connectorMinimumAmperage, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumAmperage, - }, - ), - currentPerPhaseSampledValueTemplates.L2?.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : undefined; - const phase3FluctuatedValue = isNotEmptyString( - currentPerPhaseSampledValueTemplates.L3?.value, - ) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - currentPerPhaseSampledValueTemplates.L3?.value, - connectorMaximumAmperage, - connectorMinimumAmperage, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumAmperage, - }, - ), - currentPerPhaseSampledValueTemplates.L3?.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : undefined; - currentMeasurandValues.L1 = - phase1FluctuatedValue ?? - defaultFluctuatedAmperagePerPhase ?? - getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); - currentMeasurandValues.L2 = - phase2FluctuatedValue ?? - defaultFluctuatedAmperagePerPhase ?? - getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); - currentMeasurandValues.L3 = - phase3FluctuatedValue ?? - defaultFluctuatedAmperagePerPhase ?? - getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); - } else { - currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - currentSampledValueTemplate.value, - connectorMaximumAmperage, - connectorMinimumAmperage, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumAmperage, - }, - ), - currentSampledValueTemplate.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); - currentMeasurandValues.L2 = 0; - currentMeasurandValues.L3 = 0; - } - currentMeasurandValues.allPhases = roundTo( - (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / - chargingStation.getNumberOfPhases(), - 2, - ); - break; - case CurrentType.DC: - connectorMaximumAmperage = DCElectricUtils.amperage( - connectorMaximumAvailablePower, - chargingStation.stationInfo.voltageOut!, - ); - currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - currentSampledValueTemplate.value, - connectorMaximumAmperage, - connectorMinimumAmperage, - { - limitationEnabled: - chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumAmperage, - }, - ), - currentSampledValueTemplate.fluctuationPercent ?? - Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); - break; - default: - logger.error(`${chargingStation.logPrefix()} ${errMsg}`); - throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); - } - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue( - currentSampledValueTemplate, - currentMeasurandValues.allPhases, - ), - ); - const sampledValuesIndex = meterValue.sampledValue.length - 1; - if ( - convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > - connectorMaximumAmperage || - convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < - connectorMinimumAmperage || - debug - ) { - logger.error( - `${chargingStation.logPrefix()} MeterValues measurand ${ - meterValue.sampledValue[sampledValuesIndex].measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ - meterValue.sampledValue[sampledValuesIndex].value - }/${connectorMaximumAmperage}`, - ); - } - for ( - let phase = 1; - chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); - phase++ - ) { - const phaseValue = `L${phase}`; - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue( - currentPerPhaseSampledValueTemplates[ - phaseValue as keyof MeasurandPerPhaseSampledValueTemplates - ] ?? currentSampledValueTemplate, - currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates], - undefined, - phaseValue as OCPP16MeterValuePhase, - ), - ); - const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; - if ( - convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > - connectorMaximumAmperage || - convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < - connectorMinimumAmperage || - debug - ) { - logger.error( - `${chargingStation.logPrefix()} MeterValues measurand ${ - meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: phase ${ - meterValue.sampledValue[sampledValuesPerPhaseIndex].phase - }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ - meterValue.sampledValue[sampledValuesPerPhaseIndex].value - }/${connectorMaximumAmperage}`, - ); - } - } - } - // Energy.Active.Import.Register measurand (default) - const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - ); - if (energySampledValueTemplate) { - OCPP16ServiceUtils.checkMeasurandPowerDivider( - chargingStation, - energySampledValueTemplate.measurand!, - ); - const unitDivider = - energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; - const connectorMaximumAvailablePower = - chargingStation.getConnectorMaximumAvailablePower(connectorId); - const connectorMaximumEnergyRounded = roundTo( - (connectorMaximumAvailablePower * interval) / (3600 * 1000), - 2, - ); - const connectorMinimumEnergyRounded = roundTo( - energySampledValueTemplate.minimumValue ?? 0, - 2, - ); - const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value) - ? getRandomFloatFluctuatedRounded( - OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( - energySampledValueTemplate.value, - connectorMaximumEnergyRounded, - connectorMinimumEnergyRounded, - { - limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues, - fallbackValue: connectorMinimumEnergyRounded, - unitMultiplier: unitDivider, - }, - ), - energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, - ) - : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded); - // Persist previous value on connector - if (connector) { - if ( - isNullOrUndefined(connector.energyActiveImportRegisterValue) === false && - connector.energyActiveImportRegisterValue! >= 0 && - isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false && - connector.transactionEnergyActiveImportRegisterValue! >= 0 - ) { - connector.energyActiveImportRegisterValue! += energyValueRounded; - connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded; - } else { - connector.energyActiveImportRegisterValue = 0; - connector.transactionEnergyActiveImportRegisterValue = 0; - } - } - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue( - energySampledValueTemplate, - roundTo( - chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / - unitDivider, - 2, - ), - ), - ); - const sampledValuesIndex = meterValue.sampledValue.length - 1; - if ( - energyValueRounded > connectorMaximumEnergyRounded || - energyValueRounded < connectorMinimumEnergyRounded || - debug - ) { - logger.error( - `${chargingStation.logPrefix()} MeterValues measurand ${ - meterValue.sampledValue[sampledValuesIndex].measurand ?? - OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`, - ); - } - } - return meterValue; - } - public static buildTransactionBeginMeterValue( chargingStation: ChargingStation, connectorId: number, @@ -854,37 +71,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { chargingStation, connectorId, ); - const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; + const unitDivider = + sampledValueTemplate?.unit === OCPP16MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; meterValue.sampledValue.push( OCPP16ServiceUtils.buildSampledValue( sampledValueTemplate!, roundTo((meterStart ?? 0) / unitDivider, 4), - MeterValueContext.TRANSACTION_BEGIN, - ), - ); - return meterValue; - } - - public static buildTransactionEndMeterValue( - chargingStation: ChargingStation, - connectorId: number, - meterStop: number, - ): OCPP16MeterValue { - const meterValue: OCPP16MeterValue = { - timestamp: new Date(), - sampledValue: [], - }; - // Energy.Active.Import.Register measurand (default) - const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( - chargingStation, - connectorId, - ); - const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; - meterValue.sampledValue.push( - OCPP16ServiceUtils.buildSampledValue( - sampledValueTemplate!, - roundTo((meterStop ?? 0) / unitDivider, 4), - MeterValueContext.TRANSACTION_END, + OCPP16MeterValueContext.TRANSACTION_BEGIN, ), ); return meterValue; @@ -988,7 +181,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { public static clearChargingProfiles = ( chargingStation: ChargingStation, - commandPayload: ClearChargingProfileRequest, + commandPayload: OCPP16ClearChargingProfileRequest, chargingProfiles: OCPP16ChargingProfile[] | undefined, ): boolean => { const { id, chargingProfilePurpose, stackLevel } = commandPayload; @@ -1329,79 +522,4 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils { return chargingSchedule; } }; - - private static buildSampledValue( - sampledValueTemplate: SampledValueTemplate, - value: number, - context?: MeterValueContext, - phase?: OCPP16MeterValuePhase, - ): OCPP16SampledValue { - const sampledValueContext = context ?? sampledValueTemplate?.context; - const sampledValueLocation = - sampledValueTemplate?.location ?? - OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!); - const sampledValuePhase = phase ?? sampledValueTemplate?.phase; - return { - ...(!isNullOrUndefined(sampledValueTemplate.unit) && { - unit: sampledValueTemplate.unit, - }), - ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), - ...(!isNullOrUndefined(sampledValueTemplate.measurand) && { - measurand: sampledValueTemplate.measurand, - }), - ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), - ...(!isNullOrUndefined(value) && { value: value.toString() }), - ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), - } as OCPP16SampledValue; - } - - private static checkMeasurandPowerDivider( - chargingStation: ChargingStation, - measurandType: OCPP16MeterValueMeasurand, - ): void { - if (isUndefined(chargingStation.powerDivider)) { - const errMsg = `MeterValues measurand ${ - measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: powerDivider is undefined`; - logger.error(`${chargingStation.logPrefix()} ${errMsg}`); - throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); - } else if (chargingStation?.powerDivider <= 0) { - const errMsg = `MeterValues measurand ${ - measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - }: powerDivider have zero or below value ${chargingStation.powerDivider}`; - logger.error(`${chargingStation.logPrefix()} ${errMsg}`); - throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); - } - } - - private static getMeasurandDefaultLocation( - measurandType: OCPP16MeterValueMeasurand, - ): MeterValueLocation | undefined { - switch (measurandType) { - case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: - return MeterValueLocation.EV; - } - } - - // private static getMeasurandDefaultUnit( - // measurandType: OCPP16MeterValueMeasurand, - // ): MeterValueUnit | undefined { - // switch (measurandType) { - // case OCPP16MeterValueMeasurand.CURRENT_EXPORT: - // case OCPP16MeterValueMeasurand.CURRENT_IMPORT: - // case OCPP16MeterValueMeasurand.CURRENT_OFFERED: - // return MeterValueUnit.AMP; - // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: - // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: - // return MeterValueUnit.WATT_HOUR; - // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: - // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: - // case OCPP16MeterValueMeasurand.POWER_OFFERED: - // return MeterValueUnit.WATT; - // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: - // return MeterValueUnit.PERCENT; - // case OCPP16MeterValueMeasurand.VOLTAGE: - // return MeterValueUnit.VOLT; - // } - // } } diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index 8a0b2680..27549405 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -9,7 +9,7 @@ import { OCPP16Constants } from './1.6/OCPP16Constants'; import { OCPP20Constants } from './2.0/OCPP20Constants'; import { OCPPConstants } from './OCPPConstants'; import { type ChargingStation, getConfigurationKey, getIdTagsFile } from '../../charging-station'; -import { BaseError } from '../../exception'; +import { BaseError, OCPPError } from '../../exception'; import { AuthorizationStatus, type AuthorizeRequest, @@ -18,31 +18,50 @@ import { ChargingStationEvents, type ConnectorStatus, type ConnectorStatusEnum, + CurrentType, ErrorType, FileType, IncomingRequestCommand, type JsonType, + type MeasurandPerPhaseSampledValueTemplates, + type MeasurandValues, MessageTrigger, MessageType, + type MeterValue, + MeterValueContext, + MeterValueLocation, MeterValueMeasurand, - type MeterValuePhase, + MeterValuePhase, + MeterValueUnit, type OCPP16StatusNotificationRequest, type OCPP20StatusNotificationRequest, OCPPVersion, RequestCommand, + type SampledValue, type SampledValueTemplate, StandardParametersKey, type StatusNotificationRequest, type StatusNotificationResponse, } from '../../types'; import { + ACElectricUtils, + Constants, + DCElectricUtils, + convertToFloat, + convertToInt, + getRandomFloatFluctuatedRounded, + getRandomFloatRounded, + getRandomInteger, handleFileException, isNotEmptyArray, isNotEmptyString, + isNullOrUndefined, + isUndefined, logPrefix, logger, max, min, + roundTo, } from '../../utils'; export const getMessageTypeString = (messageType: MessageType): string => { @@ -221,10 +240,980 @@ const checkConnectorStatusTransition = ( return transitionAllowed; }; +export const buildMeterValue = ( + chargingStation: ChargingStation, + connectorId: number, + transactionId: number, + interval: number, + debug = false, +): MeterValue => { + const connector = chargingStation.getConnectorStatus(connectorId); + let meterValue: MeterValue; + let socSampledValueTemplate: SampledValueTemplate | undefined; + let voltageSampledValueTemplate: SampledValueTemplate | undefined; + let powerSampledValueTemplate: SampledValueTemplate | undefined; + let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; + let currentSampledValueTemplate: SampledValueTemplate | undefined; + let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; + let energySampledValueTemplate: SampledValueTemplate | undefined; + switch (chargingStation.stationInfo?.ocppVersion) { + case OCPPVersion.VERSION_16: + meterValue = { + timestamp: new Date(), + sampledValue: [], + }; + // SoC measurand + socSampledValueTemplate = getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.STATE_OF_CHARGE, + ); + if (socSampledValueTemplate) { + const socMaximumValue = 100; + const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0; + const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value) + ? getRandomFloatFluctuatedRounded( + parseInt(socSampledValueTemplate.value), + socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : getRandomInteger(socMaximumValue, socMinimumValue); + meterValue.sampledValue.push( + buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue), + ); + const sampledValuesIndex = meterValue.sampledValue.length - 1; + if ( + convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue || + convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue || + debug + ) { + logger.error( + `${chargingStation.logPrefix()} MeterValues measurand ${ + meterValue.sampledValue[sampledValuesIndex].measurand ?? + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${ + meterValue.sampledValue[sampledValuesIndex].value + }/${socMaximumValue}`, + ); + } + } + // Voltage measurand + voltageSampledValueTemplate = getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.VOLTAGE, + ); + if (voltageSampledValueTemplate) { + const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value) + ? parseInt(voltageSampledValueTemplate.value) + : chargingStation.stationInfo.voltageOut!; + const fluctuationPercent = + voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; + const voltageMeasurandValue = getRandomFloatFluctuatedRounded( + voltageSampledValueTemplateValue, + fluctuationPercent, + ); + if ( + chargingStation.getNumberOfPhases() !== 3 || + (chargingStation.getNumberOfPhases() === 3 && + chargingStation.stationInfo?.mainVoltageMeterValues) + ) { + meterValue.sampledValue.push( + buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue), + ); + } + for ( + let phase = 1; + chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); + phase++ + ) { + const phaseLineToNeutralValue = `L${phase}-N`; + const voltagePhaseLineToNeutralSampledValueTemplate = getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.VOLTAGE, + phaseLineToNeutralValue as MeterValuePhase, + ); + let voltagePhaseLineToNeutralMeasurandValue: number | undefined; + if (voltagePhaseLineToNeutralSampledValueTemplate) { + const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString( + voltagePhaseLineToNeutralSampledValueTemplate.value, + ) + ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) + : chargingStation.stationInfo.voltageOut!; + const fluctuationPhaseToNeutralPercent = + voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT; + voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded( + voltagePhaseLineToNeutralSampledValueTemplateValue, + fluctuationPhaseToNeutralPercent, + ); + } + meterValue.sampledValue.push( + buildSampledValue( + voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate, + voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue, + undefined, + phaseLineToNeutralValue as MeterValuePhase, + ), + ); + if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) { + const phaseLineToLineValue = `L${phase}-L${ + (phase + 1) % chargingStation.getNumberOfPhases() !== 0 + ? (phase + 1) % chargingStation.getNumberOfPhases() + : chargingStation.getNumberOfPhases() + }`; + const voltagePhaseLineToLineValueRounded = roundTo( + Math.sqrt(chargingStation.getNumberOfPhases()) * + chargingStation.stationInfo.voltageOut!, + 2, + ); + const voltagePhaseLineToLineSampledValueTemplate = getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.VOLTAGE, + phaseLineToLineValue as MeterValuePhase, + ); + let voltagePhaseLineToLineMeasurandValue: number | undefined; + if (voltagePhaseLineToLineSampledValueTemplate) { + const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString( + voltagePhaseLineToLineSampledValueTemplate.value, + ) + ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) + : voltagePhaseLineToLineValueRounded; + const fluctuationPhaseLineToLinePercent = + voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT; + voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( + voltagePhaseLineToLineSampledValueTemplateValue, + fluctuationPhaseLineToLinePercent, + ); + } + const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( + voltagePhaseLineToLineValueRounded, + fluctuationPercent, + ); + meterValue.sampledValue.push( + buildSampledValue( + voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate, + voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue, + undefined, + phaseLineToLineValue as MeterValuePhase, + ), + ); + } + } + } + // Power.Active.Import measurand + powerSampledValueTemplate = getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.POWER_ACTIVE_IMPORT, + ); + if (chargingStation.getNumberOfPhases() === 3) { + powerPerPhaseSampledValueTemplates = { + L1: getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.POWER_ACTIVE_IMPORT, + MeterValuePhase.L1_N, + ), + L2: getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.POWER_ACTIVE_IMPORT, + MeterValuePhase.L2_N, + ), + L3: getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.POWER_ACTIVE_IMPORT, + MeterValuePhase.L3_N, + ), + }; + } + if (powerSampledValueTemplate) { + checkMeasurandPowerDivider(chargingStation, powerSampledValueTemplate.measurand!); + const errMsg = `MeterValues measurand ${ + powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ + chargingStation.templateFile + }, cannot calculate ${ + powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + } measurand value`; + const powerMeasurandValues: MeasurandValues = {} as MeasurandValues; + const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId); + const connectorMaximumPower = Math.round(connectorMaximumAvailablePower); + const connectorMaximumPowerPerPhase = Math.round( + connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(), + ); + const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue ?? 0); + const connectorMinimumPowerPerPhase = Math.round( + connectorMinimumPower / chargingStation.getNumberOfPhases(), + ); + switch (chargingStation.stationInfo?.currentOutType) { + case CurrentType.AC: + if (chargingStation.getNumberOfPhases() === 3) { + const defaultFluctuatedPowerPerPhase = isNotEmptyString( + powerSampledValueTemplate.value, + ) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + powerSampledValueTemplate.value, + connectorMaximumPower / unitDivider, + connectorMinimumPower / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPower / unitDivider, + }, + ) / chargingStation.getNumberOfPhases(), + powerSampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : undefined; + const phase1FluctuatedValue = isNotEmptyString( + powerPerPhaseSampledValueTemplates.L1?.value, + ) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + powerPerPhaseSampledValueTemplates.L1?.value, + connectorMaximumPowerPerPhase / unitDivider, + connectorMinimumPowerPerPhase / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPowerPerPhase / unitDivider, + }, + ), + powerPerPhaseSampledValueTemplates.L1?.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : undefined; + const phase2FluctuatedValue = isNotEmptyString( + powerPerPhaseSampledValueTemplates.L2?.value, + ) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + powerPerPhaseSampledValueTemplates.L2?.value, + connectorMaximumPowerPerPhase / unitDivider, + connectorMinimumPowerPerPhase / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPowerPerPhase / unitDivider, + }, + ), + powerPerPhaseSampledValueTemplates.L2?.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : undefined; + const phase3FluctuatedValue = isNotEmptyString( + powerPerPhaseSampledValueTemplates.L3?.value, + ) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + powerPerPhaseSampledValueTemplates.L3?.value, + connectorMaximumPowerPerPhase / unitDivider, + connectorMinimumPowerPerPhase / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPowerPerPhase / unitDivider, + }, + ), + powerPerPhaseSampledValueTemplates.L3?.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : undefined; + powerMeasurandValues.L1 = + phase1FluctuatedValue ?? + defaultFluctuatedPowerPerPhase ?? + getRandomFloatRounded( + connectorMaximumPowerPerPhase / unitDivider, + connectorMinimumPowerPerPhase / unitDivider, + ); + powerMeasurandValues.L2 = + phase2FluctuatedValue ?? + defaultFluctuatedPowerPerPhase ?? + getRandomFloatRounded( + connectorMaximumPowerPerPhase / unitDivider, + connectorMinimumPowerPerPhase / unitDivider, + ); + powerMeasurandValues.L3 = + phase3FluctuatedValue ?? + defaultFluctuatedPowerPerPhase ?? + getRandomFloatRounded( + connectorMaximumPowerPerPhase / unitDivider, + connectorMinimumPowerPerPhase / unitDivider, + ); + } else { + powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + powerSampledValueTemplate.value, + connectorMaximumPower / unitDivider, + connectorMinimumPower / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPower / unitDivider, + }, + ), + powerSampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : getRandomFloatRounded( + connectorMaximumPower / unitDivider, + connectorMinimumPower / unitDivider, + ); + powerMeasurandValues.L2 = 0; + powerMeasurandValues.L3 = 0; + } + powerMeasurandValues.allPhases = roundTo( + powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, + 2, + ); + break; + case CurrentType.DC: + powerMeasurandValues.allPhases = isNotEmptyString(powerSampledValueTemplate.value) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + powerSampledValueTemplate.value, + connectorMaximumPower / unitDivider, + connectorMinimumPower / unitDivider, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumPower / unitDivider, + }, + ), + powerSampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : getRandomFloatRounded( + connectorMaximumPower / unitDivider, + connectorMinimumPower / unitDivider, + ); + break; + default: + logger.error(`${chargingStation.logPrefix()} ${errMsg}`); + throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES); + } + meterValue.sampledValue.push( + buildSampledValue(powerSampledValueTemplate, powerMeasurandValues.allPhases), + ); + const sampledValuesIndex = meterValue.sampledValue.length - 1; + const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2); + const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2); + if ( + convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > + connectorMaximumPowerRounded || + convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < + connectorMinimumPowerRounded || + debug + ) { + logger.error( + `${chargingStation.logPrefix()} MeterValues measurand ${ + meterValue.sampledValue[sampledValuesIndex].measurand ?? + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${ + meterValue.sampledValue[sampledValuesIndex].value + }/${connectorMaximumPowerRounded}`, + ); + } + for ( + let phase = 1; + chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); + phase++ + ) { + const phaseValue = `L${phase}-N`; + meterValue.sampledValue.push( + buildSampledValue( + powerPerPhaseSampledValueTemplates[ + `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates + ] ?? powerSampledValueTemplate, + powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates], + undefined, + phaseValue as MeterValuePhase, + ), + ); + const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; + const connectorMaximumPowerPerPhaseRounded = roundTo( + connectorMaximumPowerPerPhase / unitDivider, + 2, + ); + const connectorMinimumPowerPerPhaseRounded = roundTo( + connectorMinimumPowerPerPhase / unitDivider, + 2, + ); + if ( + convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > + connectorMaximumPowerPerPhaseRounded || + convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < + connectorMinimumPowerPerPhaseRounded || + debug + ) { + logger.error( + `${chargingStation.logPrefix()} MeterValues measurand ${ + meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: phase ${ + meterValue.sampledValue[sampledValuesPerPhaseIndex].phase + }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${ + meterValue.sampledValue[sampledValuesPerPhaseIndex].value + }/${connectorMaximumPowerPerPhaseRounded}`, + ); + } + } + } + // Current.Import measurand + currentSampledValueTemplate = getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.CURRENT_IMPORT, + ); + if (chargingStation.getNumberOfPhases() === 3) { + currentPerPhaseSampledValueTemplates = { + L1: getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.CURRENT_IMPORT, + MeterValuePhase.L1, + ), + L2: getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.CURRENT_IMPORT, + MeterValuePhase.L2, + ), + L3: getSampledValueTemplate( + chargingStation, + connectorId, + MeterValueMeasurand.CURRENT_IMPORT, + MeterValuePhase.L3, + ), + }; + } + if (currentSampledValueTemplate) { + checkMeasurandPowerDivider(chargingStation, currentSampledValueTemplate.measurand!); + const errMsg = `MeterValues measurand ${ + currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ + chargingStation.templateFile + }, cannot calculate ${ + currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + } measurand value`; + const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId); + const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0; + let connectorMaximumAmperage: number; + switch (chargingStation.stationInfo?.currentOutType) { + case CurrentType.AC: + connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( + chargingStation.getNumberOfPhases(), + connectorMaximumAvailablePower, + chargingStation.stationInfo.voltageOut!, + ); + if (chargingStation.getNumberOfPhases() === 3) { + const defaultFluctuatedAmperagePerPhase = isNotEmptyString( + currentSampledValueTemplate.value, + ) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + currentSampledValueTemplate.value, + connectorMaximumAmperage, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, + ), + currentSampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : undefined; + const phase1FluctuatedValue = isNotEmptyString( + currentPerPhaseSampledValueTemplates.L1?.value, + ) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + currentPerPhaseSampledValueTemplates.L1?.value, + connectorMaximumAmperage, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, + ), + currentPerPhaseSampledValueTemplates.L1?.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : undefined; + const phase2FluctuatedValue = isNotEmptyString( + currentPerPhaseSampledValueTemplates.L2?.value, + ) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + currentPerPhaseSampledValueTemplates.L2?.value, + connectorMaximumAmperage, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, + ), + currentPerPhaseSampledValueTemplates.L2?.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : undefined; + const phase3FluctuatedValue = isNotEmptyString( + currentPerPhaseSampledValueTemplates.L3?.value, + ) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + currentPerPhaseSampledValueTemplates.L3?.value, + connectorMaximumAmperage, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, + ), + currentPerPhaseSampledValueTemplates.L3?.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : undefined; + currentMeasurandValues.L1 = + phase1FluctuatedValue ?? + defaultFluctuatedAmperagePerPhase ?? + getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); + currentMeasurandValues.L2 = + phase2FluctuatedValue ?? + defaultFluctuatedAmperagePerPhase ?? + getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); + currentMeasurandValues.L3 = + phase3FluctuatedValue ?? + defaultFluctuatedAmperagePerPhase ?? + getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); + } else { + currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + currentSampledValueTemplate.value, + connectorMaximumAmperage, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, + ), + currentSampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); + currentMeasurandValues.L2 = 0; + currentMeasurandValues.L3 = 0; + } + currentMeasurandValues.allPhases = roundTo( + (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / + chargingStation.getNumberOfPhases(), + 2, + ); + break; + case CurrentType.DC: + connectorMaximumAmperage = DCElectricUtils.amperage( + connectorMaximumAvailablePower, + chargingStation.stationInfo.voltageOut!, + ); + currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + currentSampledValueTemplate.value, + connectorMaximumAmperage, + connectorMinimumAmperage, + { + limitationEnabled: + chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumAmperage, + }, + ), + currentSampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); + break; + default: + logger.error(`${chargingStation.logPrefix()} ${errMsg}`); + throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES); + } + meterValue.sampledValue.push( + buildSampledValue(currentSampledValueTemplate, currentMeasurandValues.allPhases), + ); + const sampledValuesIndex = meterValue.sampledValue.length - 1; + if ( + convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > + connectorMaximumAmperage || + convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < + connectorMinimumAmperage || + debug + ) { + logger.error( + `${chargingStation.logPrefix()} MeterValues measurand ${ + meterValue.sampledValue[sampledValuesIndex].measurand ?? + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ + meterValue.sampledValue[sampledValuesIndex].value + }/${connectorMaximumAmperage}`, + ); + } + for ( + let phase = 1; + chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); + phase++ + ) { + const phaseValue = `L${phase}`; + meterValue.sampledValue.push( + buildSampledValue( + currentPerPhaseSampledValueTemplates[ + phaseValue as keyof MeasurandPerPhaseSampledValueTemplates + ] ?? currentSampledValueTemplate, + currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates], + undefined, + phaseValue as MeterValuePhase, + ), + ); + const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; + if ( + convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > + connectorMaximumAmperage || + convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < + connectorMinimumAmperage || + debug + ) { + logger.error( + `${chargingStation.logPrefix()} MeterValues measurand ${ + meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: phase ${ + meterValue.sampledValue[sampledValuesPerPhaseIndex].phase + }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ + meterValue.sampledValue[sampledValuesPerPhaseIndex].value + }/${connectorMaximumAmperage}`, + ); + } + } + } + // Energy.Active.Import.Register measurand (default) + energySampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId); + if (energySampledValueTemplate) { + checkMeasurandPowerDivider(chargingStation, energySampledValueTemplate.measurand!); + const unitDivider = + energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; + const connectorMaximumAvailablePower = + chargingStation.getConnectorMaximumAvailablePower(connectorId); + const connectorMaximumEnergyRounded = roundTo( + (connectorMaximumAvailablePower * interval) / (3600 * 1000), + 2, + ); + const connectorMinimumEnergyRounded = roundTo( + energySampledValueTemplate.minimumValue ?? 0, + 2, + ); + const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value) + ? getRandomFloatFluctuatedRounded( + getLimitFromSampledValueTemplateCustomValue( + energySampledValueTemplate.value, + connectorMaximumEnergyRounded, + connectorMinimumEnergyRounded, + { + limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues, + fallbackValue: connectorMinimumEnergyRounded, + unitMultiplier: unitDivider, + }, + ), + energySampledValueTemplate.fluctuationPercent ?? + Constants.DEFAULT_FLUCTUATION_PERCENT, + ) + : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded); + // Persist previous value on connector + if (connector) { + if ( + isNullOrUndefined(connector.energyActiveImportRegisterValue) === false && + connector.energyActiveImportRegisterValue! >= 0 && + isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false && + connector.transactionEnergyActiveImportRegisterValue! >= 0 + ) { + connector.energyActiveImportRegisterValue! += energyValueRounded; + connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded; + } else { + connector.energyActiveImportRegisterValue = 0; + connector.transactionEnergyActiveImportRegisterValue = 0; + } + } + meterValue.sampledValue.push( + buildSampledValue( + energySampledValueTemplate, + roundTo( + chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / + unitDivider, + 2, + ), + ), + ); + const sampledValuesIndex = meterValue.sampledValue.length - 1; + if ( + energyValueRounded > connectorMaximumEnergyRounded || + energyValueRounded < connectorMinimumEnergyRounded || + debug + ) { + logger.error( + `${chargingStation.logPrefix()} MeterValues measurand ${ + meterValue.sampledValue[sampledValuesIndex].measurand ?? + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`, + ); + } + } + return meterValue; + case OCPPVersion.VERSION_20: + case OCPPVersion.VERSION_201: + default: + throw new BaseError( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`, + ); + } +}; + +export const buildTransactionEndMeterValue = ( + chargingStation: ChargingStation, + connectorId: number, + meterStop: number, +): MeterValue => { + let meterValue: MeterValue; + let sampledValueTemplate: SampledValueTemplate | undefined; + let unitDivider: number; + switch (chargingStation.stationInfo?.ocppVersion) { + case OCPPVersion.VERSION_16: + meterValue = { + timestamp: new Date(), + sampledValue: [], + }; + // Energy.Active.Import.Register measurand (default) + sampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId); + unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; + meterValue.sampledValue.push( + buildSampledValue( + sampledValueTemplate!, + roundTo((meterStop ?? 0) / unitDivider, 4), + MeterValueContext.TRANSACTION_END, + ), + ); + return meterValue; + case OCPPVersion.VERSION_20: + case OCPPVersion.VERSION_201: + default: + throw new BaseError( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`, + ); + } +}; + +const checkMeasurandPowerDivider = ( + chargingStation: ChargingStation, + measurandType: MeterValueMeasurand, +): void => { + if (isUndefined(chargingStation.powerDivider)) { + const errMsg = `MeterValues measurand ${ + measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: powerDivider is undefined`; + logger.error(`${chargingStation.logPrefix()} ${errMsg}`); + throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES); + } else if (chargingStation?.powerDivider <= 0) { + const errMsg = `MeterValues measurand ${ + measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + }: powerDivider have zero or below value ${chargingStation.powerDivider}`; + logger.error(`${chargingStation.logPrefix()} ${errMsg}`); + throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES); + } +}; + +const getLimitFromSampledValueTemplateCustomValue = ( + value: string | undefined, + maxLimit: number, + minLimit: number, + options?: { limitationEnabled?: boolean; fallbackValue?: number; unitMultiplier?: number }, +): number => { + options = { + ...{ + limitationEnabled: false, + unitMultiplier: 1, + fallbackValue: 0, + }, + ...options, + }; + const parsedValue = parseInt(value ?? ''); + if (options?.limitationEnabled) { + return max( + min((!isNaN(parsedValue) ? parsedValue : Infinity) * options.unitMultiplier!, maxLimit), + minLimit, + ); + } + return (!isNaN(parsedValue) ? parsedValue : options.fallbackValue!) * options.unitMultiplier!; +}; + +const getSampledValueTemplate = ( + chargingStation: ChargingStation, + connectorId: number, + measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, + phase?: MeterValuePhase, +): SampledValueTemplate | undefined => { + const onPhaseStr = phase ? `on phase ${phase} ` : ''; + if (OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes(measurand) === false) { + logger.warn( + `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`, + ); + return; + } + if ( + measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && + getConfigurationKey( + chargingStation, + StandardParametersKey.MeterValuesSampledData, + )?.value?.includes(measurand) === false + ) { + logger.debug( + `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${ + StandardParametersKey.MeterValuesSampledData + }' OCPP parameter`, + ); + return; + } + const sampledValueTemplates: SampledValueTemplate[] = + chargingStation.getConnectorStatus(connectorId)!.MeterValues; + for ( + let index = 0; + isNotEmptyArray(sampledValueTemplates) === true && index < sampledValueTemplates.length; + index++ + ) { + if ( + OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes( + sampledValueTemplates[index]?.measurand ?? + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, + ) === false + ) { + logger.warn( + `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`, + ); + } else if ( + phase && + sampledValueTemplates[index]?.phase === phase && + sampledValueTemplates[index]?.measurand === measurand && + getConfigurationKey( + chargingStation, + StandardParametersKey.MeterValuesSampledData, + )?.value?.includes(measurand) === true + ) { + return sampledValueTemplates[index]; + } else if ( + !phase && + !sampledValueTemplates[index]?.phase && + sampledValueTemplates[index]?.measurand === measurand && + getConfigurationKey( + chargingStation, + StandardParametersKey.MeterValuesSampledData, + )?.value?.includes(measurand) === true + ) { + return sampledValueTemplates[index]; + } else if ( + measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && + (!sampledValueTemplates[index]?.measurand || + sampledValueTemplates[index]?.measurand === measurand) + ) { + return sampledValueTemplates[index]; + } + } + if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) { + const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`; + logger.error(`${chargingStation.logPrefix()} ${errorMsg}`); + throw new BaseError(errorMsg); + } + logger.debug( + `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`, + ); +}; + +const buildSampledValue = ( + sampledValueTemplate: SampledValueTemplate, + value: number, + context?: MeterValueContext, + phase?: MeterValuePhase, +): SampledValue => { + const sampledValueContext = context ?? sampledValueTemplate?.context; + const sampledValueLocation = + sampledValueTemplate?.location ?? getMeasurandDefaultLocation(sampledValueTemplate.measurand!); + const sampledValuePhase = phase ?? sampledValueTemplate?.phase; + return { + ...(!isNullOrUndefined(sampledValueTemplate.unit) && { + unit: sampledValueTemplate.unit, + }), + ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), + ...(!isNullOrUndefined(sampledValueTemplate.measurand) && { + measurand: sampledValueTemplate.measurand, + }), + ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), + ...(!isNullOrUndefined(value) && { value: value.toString() }), + ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), + } as SampledValue; +}; + +const getMeasurandDefaultLocation = ( + measurandType: MeterValueMeasurand, +): MeterValueLocation | undefined => { + switch (measurandType) { + case MeterValueMeasurand.STATE_OF_CHARGE: + return MeterValueLocation.EV; + } +}; + +// const getMeasurandDefaultUnit = ( +// measurandType: MeterValueMeasurand, +// ): MeterValueUnit | undefined => { +// switch (measurandType) { +// case MeterValueMeasurand.CURRENT_EXPORT: +// case MeterValueMeasurand.CURRENT_IMPORT: +// case MeterValueMeasurand.CURRENT_OFFERED: +// return MeterValueUnit.AMP; +// case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: +// case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: +// return MeterValueUnit.WATT_HOUR; +// case MeterValueMeasurand.POWER_ACTIVE_EXPORT: +// case MeterValueMeasurand.POWER_ACTIVE_IMPORT: +// case MeterValueMeasurand.POWER_OFFERED: +// return MeterValueUnit.WATT; +// case MeterValueMeasurand.STATE_OF_CHARGE: +// return MeterValueUnit.PERCENT; +// case MeterValueMeasurand.VOLTAGE: +// return MeterValueUnit.VOLT; +// } +// }; + export class OCPPServiceUtils { public static getMessageTypeString = getMessageTypeString; public static sendAndSetConnectorStatus = sendAndSetConnectorStatus; public static isIdTagAuthorized = isIdTagAuthorized; + public static buildTransactionEndMeterValue = buildTransactionEndMeterValue; + protected static getSampledValueTemplate = getSampledValueTemplate; + protected static buildSampledValue = buildSampledValue; protected constructor() { // This is intentional @@ -365,111 +1354,6 @@ export class OCPPServiceUtils { } } - protected static getSampledValueTemplate( - chargingStation: ChargingStation, - connectorId: number, - measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, - phase?: MeterValuePhase, - ): SampledValueTemplate | undefined { - const onPhaseStr = phase ? `on phase ${phase} ` : ''; - if (OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes(measurand) === false) { - logger.warn( - `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`, - ); - return; - } - if ( - measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && - getConfigurationKey( - chargingStation, - StandardParametersKey.MeterValuesSampledData, - )?.value?.includes(measurand) === false - ) { - logger.debug( - `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${ - StandardParametersKey.MeterValuesSampledData - }' OCPP parameter`, - ); - return; - } - const sampledValueTemplates: SampledValueTemplate[] = - chargingStation.getConnectorStatus(connectorId)!.MeterValues; - for ( - let index = 0; - isNotEmptyArray(sampledValueTemplates) === true && index < sampledValueTemplates.length; - index++ - ) { - if ( - OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes( - sampledValueTemplates[index]?.measurand ?? - MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, - ) === false - ) { - logger.warn( - `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`, - ); - } else if ( - phase && - sampledValueTemplates[index]?.phase === phase && - sampledValueTemplates[index]?.measurand === measurand && - getConfigurationKey( - chargingStation, - StandardParametersKey.MeterValuesSampledData, - )?.value?.includes(measurand) === true - ) { - return sampledValueTemplates[index]; - } else if ( - !phase && - !sampledValueTemplates[index]?.phase && - sampledValueTemplates[index]?.measurand === measurand && - getConfigurationKey( - chargingStation, - StandardParametersKey.MeterValuesSampledData, - )?.value?.includes(measurand) === true - ) { - return sampledValueTemplates[index]; - } else if ( - measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && - (!sampledValueTemplates[index]?.measurand || - sampledValueTemplates[index]?.measurand === measurand) - ) { - return sampledValueTemplates[index]; - } - } - if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) { - const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`; - logger.error(`${chargingStation.logPrefix()} ${errorMsg}`); - throw new BaseError(errorMsg); - } - logger.debug( - `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`, - ); - } - - protected static getLimitFromSampledValueTemplateCustomValue( - value: string | undefined, - maxLimit: number, - minLimit: number, - options?: { limitationEnabled?: boolean; fallbackValue?: number; unitMultiplier?: number }, - ): number { - options = { - ...{ - limitationEnabled: false, - unitMultiplier: 1, - fallbackValue: 0, - }, - ...options, - }; - const parsedValue = parseInt(value ?? ''); - if (options?.limitationEnabled) { - return max( - min((!isNaN(parsedValue) ? parsedValue : Infinity) * options.unitMultiplier!, maxLimit), - minLimit, - ); - } - return (!isNaN(parsedValue) ? parsedValue : options.fallbackValue!) * options.unitMultiplier!; - } - private static logPrefix = ( ocppVersion: OCPPVersion, moduleName?: string, diff --git a/src/charging-station/ocpp/index.ts b/src/charging-station/ocpp/index.ts index 2f21bd2e..b5abe464 100644 --- a/src/charging-station/ocpp/index.ts +++ b/src/charging-station/ocpp/index.ts @@ -1,15 +1,15 @@ export { OCPP16IncomingRequestService } from './1.6/OCPP16IncomingRequestService'; export { OCPP16RequestService } from './1.6/OCPP16RequestService'; export { OCPP16ResponseService } from './1.6/OCPP16ResponseService'; -// FIXME: shall not be exported -export { OCPP16ServiceUtils } from './1.6/OCPP16ServiceUtils'; export { OCPP20IncomingRequestService } from './2.0/OCPP20IncomingRequestService'; export { OCPP20RequestService } from './2.0/OCPP20RequestService'; export { OCPP20ResponseService } from './2.0/OCPP20ResponseService'; export { OCPPIncomingRequestService } from './OCPPIncomingRequestService'; export { OCPPRequestService } from './OCPPRequestService'; export { + buildMeterValue, buildStatusNotificationRequest, + buildTransactionEndMeterValue, getMessageTypeString, isIdTagAuthorized, sendAndSetConnectorStatus, diff --git a/src/types/index.ts b/src/types/index.ts index a707b46b..cc80f527 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -81,7 +81,6 @@ export { } from './WorkerBroadcastChannel'; export { type ChangeConfigurationRequest, - type ClearChargingProfileRequest, type GetConfigurationRequest, type GetDiagnosticsRequest, OCPP16AvailabilityType, @@ -89,6 +88,7 @@ export { type OCPP16CancelReservationRequest, type OCPP16ChangeAvailabilityRequest, type OCPP16ClearCacheRequest, + type OCPP16ClearChargingProfileRequest, type OCPP16DataTransferRequest, OCPP16DataTransferVendorId, type OCPP16DiagnosticsStatusNotificationRequest, @@ -111,11 +111,11 @@ export { } from './ocpp/1.6/Requests'; export { type ChangeConfigurationResponse, - type ClearChargingProfileResponse, type GetConfigurationResponse, type GetDiagnosticsResponse, type OCPP16BootNotificationResponse, type OCPP16ChangeAvailabilityResponse, + type OCPP16ClearChargingProfileResponse, type OCPP16DataTransferResponse, OCPP16DataTransferStatus, type OCPP16DiagnosticsStatusNotificationResponse, @@ -198,14 +198,22 @@ export type { } from './MeasurandPerPhaseSampledValueTemplates'; export type { MeasurandValues } from './MeasurandValues'; export { MessageType } from './ocpp/MessageType'; -export { type MeterValue, MeterValueMeasurand, MeterValuePhase } from './ocpp/MeterValues'; export { + type MeterValue, MeterValueContext, MeterValueLocation, + MeterValueMeasurand, + MeterValuePhase, MeterValueUnit, + type SampledValue, +} from './ocpp/MeterValues'; +export { type OCPP16MeterValue, + OCPP16MeterValueContext, + OCPP16MeterValueLocation, OCPP16MeterValueMeasurand, OCPP16MeterValuePhase, + OCPP16MeterValueUnit, type OCPP16MeterValuesRequest, type OCPP16MeterValuesResponse, type OCPP16SampledValue, diff --git a/src/types/ocpp/1.6/MeterValues.ts b/src/types/ocpp/1.6/MeterValues.ts index 74698284..6d102b88 100644 --- a/src/types/ocpp/1.6/MeterValues.ts +++ b/src/types/ocpp/1.6/MeterValues.ts @@ -1,7 +1,7 @@ import type { EmptyObject } from '../../EmptyObject'; import type { JsonObject } from '../../JsonType'; -export enum MeterValueUnit { +export enum OCPP16MeterValueUnit { WATT_HOUR = 'Wh', KILO_WATT_HOUR = 'kWh', VAR_HOUR = 'varh', @@ -20,7 +20,7 @@ export enum MeterValueUnit { PERCENT = 'Percent', } -export enum MeterValueContext { +export enum OCPP16MeterValueContext { INTERRUPTION_BEGIN = 'Interruption.Begin', INTERRUPTION_END = 'Interruption.End', OTHER = 'Other', @@ -56,7 +56,7 @@ export enum OCPP16MeterValueMeasurand { VOLTAGE = 'Voltage', } -export enum MeterValueLocation { +export enum OCPP16MeterValueLocation { BODY = 'Body', CABLE = 'Cable', EV = 'EV', @@ -77,19 +77,19 @@ export enum OCPP16MeterValuePhase { L3_L1 = 'L3-L1', } -enum MeterValueFormat { +enum OCPP16MeterValueFormat { RAW = 'Raw', SIGNED_DATA = 'SignedData', } export interface OCPP16SampledValue extends JsonObject { value: string; - unit?: MeterValueUnit; - context?: MeterValueContext; + unit?: OCPP16MeterValueUnit; + context?: OCPP16MeterValueContext; measurand?: OCPP16MeterValueMeasurand; phase?: OCPP16MeterValuePhase; - location?: MeterValueLocation; - format?: MeterValueFormat; + location?: OCPP16MeterValueLocation; + format?: OCPP16MeterValueFormat; } export interface OCPP16MeterValue extends JsonObject { diff --git a/src/types/ocpp/1.6/Requests.ts b/src/types/ocpp/1.6/Requests.ts index c6f041e5..b14583f0 100644 --- a/src/types/ocpp/1.6/Requests.ts +++ b/src/types/ocpp/1.6/Requests.ts @@ -124,7 +124,7 @@ export interface OCPP16ChangeAvailabilityRequest extends JsonObject { type: OCPP16AvailabilityType; } -export interface ClearChargingProfileRequest extends JsonObject { +export interface OCPP16ClearChargingProfileRequest extends JsonObject { id?: number; connectorId?: number; chargingProfilePurpose?: OCPP16ChargingProfilePurposeType; diff --git a/src/types/ocpp/1.6/Responses.ts b/src/types/ocpp/1.6/Responses.ts index c98c03a3..a42733e8 100644 --- a/src/types/ocpp/1.6/Responses.ts +++ b/src/types/ocpp/1.6/Responses.ts @@ -74,7 +74,7 @@ export enum OCPP16ClearChargingProfileStatus { UNKNOWN = 'Unknown', } -export interface ClearChargingProfileResponse extends JsonObject { +export interface OCPP16ClearChargingProfileResponse extends JsonObject { status: OCPP16ClearChargingProfileStatus; } diff --git a/src/types/ocpp/MeterValues.ts b/src/types/ocpp/MeterValues.ts index f34a8c17..78cc9af9 100644 --- a/src/types/ocpp/MeterValues.ts +++ b/src/types/ocpp/MeterValues.ts @@ -1,15 +1,33 @@ import { type OCPP16MeterValue, + OCPP16MeterValueContext, + OCPP16MeterValueLocation, OCPP16MeterValueMeasurand, OCPP16MeterValuePhase, + OCPP16MeterValueUnit, type OCPP16SampledValue, } from './1.6/MeterValues'; +export const MeterValueUnit = { + ...OCPP16MeterValueUnit, +} as const; +export type MeterValueUnit = OCPP16MeterValueUnit; + +export const MeterValueContext = { + ...OCPP16MeterValueContext, +} as const; +export type MeterValueContext = OCPP16MeterValueContext; + export const MeterValueMeasurand = { ...OCPP16MeterValueMeasurand, } as const; export type MeterValueMeasurand = OCPP16MeterValueMeasurand; +export const MeterValueLocation = { + ...OCPP16MeterValueLocation, +} as const; +export type MeterValueLocation = OCPP16MeterValueLocation; + export const MeterValuePhase = { ...OCPP16MeterValuePhase, } as const; diff --git a/ui/web/pnpm-lock.yaml b/ui/web/pnpm-lock.yaml index 8facfa35..93b4bbc0 100644 --- a/ui/web/pnpm-lock.yaml +++ b/ui/web/pnpm-lock.yaml @@ -1469,7 +1469,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001566 - electron-to-chromium: 1.4.601 + electron-to-chromium: 1.4.603 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.22.2) dev: true @@ -1783,8 +1783,8 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false - /electron-to-chromium@1.4.601: - resolution: {integrity: sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==} + /electron-to-chromium@1.4.603: + resolution: {integrity: sha512-Dvo5OGjnl7AZTU632dFJtWj0uJK835eeOVQIuRcmBmsFsTNn3cL05FqOyHAfGQDIoHfLhyJ1Tya3PJ0ceMz54g==} dev: true /emoji-regex@8.0.0: -- 2.34.1