From 9ccca265277990663dad8ad0573b7c283e221c6f Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sat, 19 Jun 2021 21:47:35 +0200 Subject: [PATCH] Add per phase support to MeterValues in template. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit + Add support for static value + fluctuation + Add support for line to line voltage Signed-off-by: Jérôme Benoit --- README.md | 1 + package-lock.json | 50 +-- package.json | 14 +- src/charging-station/ChargingStation.ts | 39 ++- .../ocpp/1.6/OCCP16IncomingRequestService.ts | 3 +- .../ocpp/1.6/OCPP16RequestService.ts | 323 +++++++++++------- .../ocpp/1.6/OCPP16ServiceUtils.ts | 33 +- src/types/ChargingStationTemplate.ts | 2 +- src/types/ConfigurationData.ts | 1 - src/types/Connectors.ts | 6 +- .../MeasurandPerPhaseSampledValueTemplates.ts | 7 + src/types/ocpp/1.6/MeterValues.ts | 4 +- src/types/ocpp/1.6/Responses.ts | 31 +- src/types/ocpp/MeterValues.ts | 8 +- src/types/ocpp/Responses.ts | 41 ++- src/utils/Configuration.ts | 9 +- src/utils/Constants.ts | 9 +- src/utils/Utils.ts | 7 + 18 files changed, 367 insertions(+), 221 deletions(-) create mode 100644 src/types/MeasurandPerPhaseSampledValueTemplates.ts diff --git a/README.md b/README.md index db22f0e1..a16ad55b 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ beginEndMeterValues | true/false | false | boolean | enable Transaction.{Begin,E outOfOrderEndMeterValues | true/false | false | boolean | send Transaction.End MeterValues out of order meteringPerTransaction | true/false | true | boolean | disable metering on a per transaction basis transactionDataMeterValues | true/false | false | boolean | enable transaction data MeterValues at stop transaction +mainVoltageMeterValues | true/false | true | boolean | include charging station main voltage MeterValues on three phased charging stations Configuration | | | ChargingStationConfiguration | charging stations OCPP configuration parameters AutomaticTransactionGenerator | | | AutomaticTransactionGenerator | charging stations ATG configuration Connectors | | | Connectors | charging stations connectors configuration diff --git a/package-lock.json b/package-lock.json index a7732ebb..929f04e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1873,9 +1873,9 @@ "dev": true }, "@types/ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-8mbDgtc8xpxDDem5Gwj76stBDJX35KQ3YBoayxlqUQcL5BZUthiqP/VQ4PQnLHqM4PmlbyO74t98eJpURO+gPA==", "dev": true, "requires": { "@types/node": "*" @@ -5367,9 +5367,9 @@ } }, "eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", @@ -5912,9 +5912,9 @@ } }, "eslint-plugin-jsdoc": { - "version": "35.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-35.2.0.tgz", - "integrity": "sha512-kMka7QWeQkgenwfazdgmdiYojS2QMI+pWtv/+3gjQJBdCWGPNXPmPQr+WO5slhiTRA+3MO3b2ZnBflXtUmq7wA==", + "version": "35.3.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-35.3.2.tgz", + "integrity": "sha512-r3qfbByMZ4O5Y8O6aHxo1AdgdpwTQSD72anIZuhjNI6ZkMtjhpSkkPN3cY/tsZtmWeMH+1pB/nblWIAr3F0lxA==", "dev": true, "requires": { "@es-joy/jsdoccomment": "^0.8.0-alpha.2", @@ -9183,9 +9183,9 @@ "dev": true }, "mocha": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.0.tgz", - "integrity": "sha512-GRGG/q9bIaUkHJB9NL+KZNjDhMBHB30zW3bZW9qOiYr+QChyLjPzswaxFWkI1q6lGlSL28EQYzAi2vKWNkPx+g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.1.tgz", + "integrity": "sha512-9zwsavlRO+5csZu6iRtl3GHImAbhERoDsZwdRkdJ/bE+eVplmoxNKE901ZJ9LdSchYBjSCPbjKc5XvcAri2ylw==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -13213,12 +13213,12 @@ } }, "rollup": { - "version": "2.51.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz", - "integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==", + "version": "2.52.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.1.tgz", + "integrity": "sha512-/SPqz8UGnp4P1hq6wc9gdTqA2bXQXGx13TtoL03GBm6qGRI6Hm3p4Io7GeiHNLl0BsQAne1JNYY+q/apcY933w==", "dev": true, "requires": { - "fsevents": "~2.3.1" + "fsevents": "~2.3.2" } }, "rollup-plugin-analyzer": { @@ -14233,9 +14233,9 @@ }, "dependencies": { "ajv": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", - "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -14701,9 +14701,9 @@ } }, "typescript": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", - "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", "dev": true }, "uglify-js": { @@ -15243,9 +15243,9 @@ } }, "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", + "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==" }, "xdg-basedir": { "version": "3.0.0", diff --git a/package.json b/package.json index 1da708ac..c16520c1 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "uuid": "^8.3.2", "winston": "^3.3.3", "winston-daily-rotate-file": "^4.5.5", - "ws": "^7.4.6" + "ws": "^7.5.0" }, "optionalDependencies": { "bufferutil": "^4.0.3", @@ -77,24 +77,24 @@ "@types/mochawesome": "^6.2.0", "@types/node": "^14.17.3", "@types/uuid": "^8.3.0", - "@types/ws": "^7.4.4", + "@types/ws": "^7.4.5", "@typescript-eslint/eslint-plugin": "^4.27.0", "@typescript-eslint/parser": "^4.27.0", "auto-changelog": "^2.3.0", "clinic": "^9.0.0", "cross-env": "^7.0.3", - "eslint": "^7.28.0", + "eslint": "^7.29.0", "eslint-plugin-import": "^2.23.4", - "eslint-plugin-jsdoc": "^35.2.0", + "eslint-plugin-jsdoc": "^35.3.2", "eslint-plugin-node": "^11.1.0", "expect": "^27.0.2", "mbt": "^1.2.1", - "mocha": "^9.0.0", + "mocha": "^9.0.1", "mochawesome": "^6.2.2", "npm-check": "^5.9.2", "nyc": "^15.1.0", "release-it": "^14.9.0", - "rollup": "^2.51.2", + "rollup": "^2.52.1", "rollup-plugin-analyzer": "^4.0.0", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-delete": "^2.0.0", @@ -102,6 +102,6 @@ "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.30.0", "ts-node": "^10.0.0", - "typescript": "^4.3.2" + "typescript": "^4.3.4" } } diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index ed9b79f0..6bab3d86 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -2,7 +2,8 @@ import { BootNotificationResponse, RegistrationStatus } from '../types/ocpp/Resp import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration'; import ChargingStationTemplate, { CurrentOutType, PowerUnits, VoltageOut } from '../types/ChargingStationTemplate'; import { ConnectorPhaseRotation, StandardParametersKey, SupportedFeatureProfiles } from '../types/ocpp/Configuration'; -import Connectors, { Connector } from '../types/Connectors'; +import Connectors, { Connector, SampledValueTemplate } from '../types/Connectors'; +import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues'; import { PerformanceObserver, performance } from 'perf_hooks'; import Requests, { AvailabilityType, BootNotificationRequest, IncomingRequest, IncomingRequestCommand } from '../types/ocpp/Requests'; import WebSocket, { MessageEvent } from 'ws'; @@ -15,7 +16,6 @@ import Configuration from '../utils/Configuration'; import Constants from '../utils/Constants'; import FileUtils from '../utils/FileUtils'; import { MessageType } from '../types/ocpp/MessageType'; -import { MeterValueMeasurand } from '../types/ocpp/MeterValues'; import OCPP16IncomingRequestService from './ocpp/1.6/OCCP16IncomingRequestService'; import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService'; import OCPP16ResponseService from './ocpp/1.6/OCPP16ResponseService'; @@ -165,6 +165,10 @@ export default class ChargingStation { return this.stationInfo.transactionDataMeterValues ?? false; } + public getMainVoltageMeterValues(): boolean { + return this.stationInfo.mainVoltageMeterValues ?? true; + } + public getEnergyActiveImportRegisterByTransactionId(transactionId: number): number | undefined { if (this.getMeteringPerTransaction()) { for (const connector in this.connectors) { @@ -204,6 +208,33 @@ export default class ChargingStation { this.startWebSocketPing(); } + public getSampledValueTemplate(connectorId: number, measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, + phase?: MeterValuePhase): SampledValueTemplate | undefined { + if (!Constants.SUPPORTED_MEASURANDS.includes(measurand)) { + logger.warn(`${this.logPrefix()} Unsupported MeterValues measurand ${measurand} in template on connectorId ${connectorId}`); + return; + } + const sampledValueTemplates: SampledValueTemplate[] = this.getConnector(connectorId).MeterValues; + let defaultMeasurandFound = false; + for (let index = 0; !Utils.isEmptyArray(sampledValueTemplates) && index < sampledValueTemplates.length; index++) { + if (phase && sampledValueTemplates[index]?.phase === phase && sampledValueTemplates[index]?.measurand === measurand + && this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(measurand)) { + return sampledValueTemplates[index]; + } else if (!phase && !sampledValueTemplates[index].phase && sampledValueTemplates[index]?.measurand === measurand + && this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(measurand)) { + return sampledValueTemplates[index]; + } else if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + && (!sampledValueTemplates[index].measurand || sampledValueTemplates[index].measurand === measurand)) { + defaultMeasurandFound = true; + return sampledValueTemplates[index]; + } + } + if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && !defaultMeasurandFound) { + logger.error(`${this.logPrefix()} Missing MeterValues for default measurand ${MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} in template on connectorId ${connectorId}`); + } + logger.debug(`${this.logPrefix()} No MeterValues for measurand ${measurand} ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`); + } + public getAutomaticTransactionGeneratorRequireAuthorize(): boolean { return this.stationInfo.AutomaticTransactionGenerator.requireAuthorize ?? true; } @@ -384,9 +415,9 @@ export default class ChargingStation { private getChargingStationId(stationTemplate: ChargingStationTemplate): string { // In case of multiple instances: add instance index to charging station id - let instanceIndex = process.env.CF_INSTANCE_INDEX ? process.env.CF_INSTANCE_INDEX : 0; + let instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0; instanceIndex = instanceIndex > 0 ? instanceIndex : ''; - const idSuffix = stationTemplate.nameSuffix ? stationTemplate.nameSuffix : ''; + const idSuffix = stationTemplate.nameSuffix ?? ''; return stationTemplate.fixedName ? stationTemplate.baseName : stationTemplate.baseName + '-' + instanceIndex.toString() + ('000000000' + this.index.toString()).substr(('000000000' + this.index.toString()).length - 4) + idSuffix; } diff --git a/src/charging-station/ocpp/1.6/OCCP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCCP16IncomingRequestService.ts index 7c32cb78..0ef5fa0c 100644 --- a/src/charging-station/ocpp/1.6/OCCP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCCP16IncomingRequestService.ts @@ -1,9 +1,10 @@ import { ChangeAvailabilityRequest, ChangeConfigurationRequest, ClearChargingProfileRequest, GetConfigurationRequest, OCPP16AvailabilityType, OCPP16IncomingRequestCommand, RemoteStartTransactionRequest, RemoteStopTransactionRequest, ResetRequest, SetChargingProfileRequest, UnlockConnectorRequest } from '../../../types/ocpp/1.6/Requests'; -import { ChangeAvailabilityResponse, ChangeConfigurationResponse, ClearChargingProfileResponse, DefaultResponse, GetConfigurationResponse, SetChargingProfileResponse, UnlockConnectorResponse } from '../../../types/ocpp/1.6/Responses'; +import { ChangeAvailabilityResponse, ChangeConfigurationResponse, ClearChargingProfileResponse, GetConfigurationResponse, SetChargingProfileResponse, UnlockConnectorResponse } from '../../../types/ocpp/1.6/Responses'; import { ChargingProfilePurposeType, OCPP16ChargingProfile } from '../../../types/ocpp/1.6/ChargingProfile'; import { OCPP16AuthorizationStatus, OCPP16StopTransactionReason } from '../../../types/ocpp/1.6/Transaction'; import Constants from '../../../utils/Constants'; +import { DefaultResponse } from '../../../types/ocpp/Responses'; import { ErrorType } from '../../../types/ocpp/ErrorType'; import { MessageType } from '../../../types/ocpp/MessageType'; import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus'; diff --git a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts index 2e8b540b..5221d507 100644 --- a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts @@ -1,17 +1,17 @@ import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils'; import { AuthorizeRequest, OCPP16AuthorizeResponse, OCPP16StartTransactionResponse, OCPP16StopTransactionReason, OCPP16StopTransactionResponse, StartTransactionRequest, StopTransactionRequest } from '../../../types/ocpp/1.6/Transaction'; +import { CurrentOutType, VoltageOut } from '../../../types/ChargingStationTemplate'; import { HeartbeatRequest, OCPP16BootNotificationRequest, OCPP16IncomingRequestCommand, OCPP16RequestCommand, StatusNotificationRequest } from '../../../types/ocpp/1.6/Requests'; -import { MeterValuePhase, MeterValueUnit, MeterValuesRequest, OCPP16MeterValue, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues'; +import { MeterValueUnit, MeterValuesRequest, OCPP16MeterValue, OCPP16MeterValueMeasurand, OCPP16MeterValuePhase } from '../../../types/ocpp/1.6/MeterValues'; import Constants from '../../../utils/Constants'; -import { CurrentOutType } from '../../../types/ChargingStationTemplate'; +import MeasurandPerPhaseSampledValueTemplates from '../../../types/MeasurandPerPhaseSampledValueTemplates'; import MeasurandValues from '../../../types/MeasurandValues'; import { MessageType } from '../../../types/ocpp/MessageType'; import { OCPP16BootNotificationResponse } from '../../../types/ocpp/1.6/Responses'; import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode'; import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus'; import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; -import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration'; import OCPPError from '../../OcppError'; import OCPPRequestService from '../OCPPRequestService'; import Utils from '../../../utils/Utils'; @@ -120,138 +120,205 @@ export default class OCPP16RequestService extends OCPPRequestService { timestamp: new Date().toISOString(), sampledValue: [], }; - const meterValuesTemplate: OCPP16SampledValue[] = self.chargingStation.getConnector(connectorId).MeterValues; - for (let index = 0; index < meterValuesTemplate.length; index++) { - const connector = self.chargingStation.getConnector(connectorId); - // SoC measurand - if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.STATE_OF_CHARGE && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.STATE_OF_CHARGE)) { - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], Utils.getRandomInt(100))); - const sampledValuesIndex = meterValue.sampledValue.length - 1; - if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) { - logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/100`); - } - // Voltage measurand - } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.VOLTAGE && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.VOLTAGE)) { - const voltageMeasurandValue = Utils.getRandomFloatRounded(self.chargingStation.getVoltageOut() + self.chargingStation.getVoltageOut() * 0.1, self.chargingStation.getVoltageOut() - self.chargingStation.getVoltageOut() * 0.1); - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], voltageMeasurandValue)); - for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) { - let phaseValue: string; - if (self.chargingStation.getVoltageOut() >= 0 && self.chargingStation.getVoltageOut() <= 250) { - phaseValue = `L${phase}-N`; - } else if (self.chargingStation.getVoltageOut() > 250) { - phaseValue = `L${phase}-L${(phase + 1) % self.chargingStation.getNumberOfPhases() !== 0 ? (phase + 1) % self.chargingStation.getNumberOfPhases() : self.chargingStation.getNumberOfPhases()}`; - } - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], voltageMeasurandValue, null, - phaseValue as MeterValuePhase)); - } - // Power.Active.Import measurand - } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT)) { - OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand); - const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`; - const powerMeasurandValues = {} as MeasurandValues; - const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; - const maxPower = Math.round(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider); - const maxPowerPerPhase = Math.round((self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider) / self.chargingStation.getNumberOfPhases()); - switch (self.chargingStation.getCurrentOutType()) { - case CurrentOutType.AC: - if (Utils.isUndefined(meterValuesTemplate[index].value)) { - powerMeasurandValues.L1 = Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); - powerMeasurandValues.L2 = 0; - powerMeasurandValues.L3 = 0; - if (self.chargingStation.getNumberOfPhases() === 3) { - powerMeasurandValues.L2 = Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); - powerMeasurandValues.L3 = Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); - } - powerMeasurandValues.allPhases = Utils.roundTo(powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, 2); - } - break; - case CurrentOutType.DC: - if (Utils.isUndefined(meterValuesTemplate[index].value)) { - powerMeasurandValues.allPhases = Utils.getRandomFloatRounded(maxPower / unitDivider); - } - break; - default: - logger.error(errMsg); - throw Error(errMsg); - } - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], powerMeasurandValues.allPhases)); - const sampledValuesIndex = meterValue.sampledValue.length - 1; - const maxPowerRounded = Utils.roundTo(maxPower / unitDivider, 2); - if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxPowerRounded || debug) { - logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxPowerRounded}`); - } - for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) { - const phaseValue = `L${phase}-N`; - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], powerMeasurandValues[`L${phase}`], null, - phaseValue as MeterValuePhase)); - } - // Current.Import measurand - } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.CURRENT_IMPORT && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.CURRENT_IMPORT)) { - OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand); - const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`; - const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; - let maxAmperage: number; - switch (self.chargingStation.getCurrentOutType()) { - case CurrentOutType.AC: - maxAmperage = ACElectricUtils.amperagePerPhaseFromPower(self.chargingStation.getNumberOfPhases(), self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider, self.chargingStation.getVoltageOut()); - if (Utils.isUndefined(meterValuesTemplate[index].value)) { - currentMeasurandValues.L1 = Utils.getRandomFloatRounded(maxAmperage); - currentMeasurandValues.L2 = 0; - currentMeasurandValues.L3 = 0; - if (self.chargingStation.getNumberOfPhases() === 3) { - currentMeasurandValues.L2 = Utils.getRandomFloatRounded(maxAmperage); - currentMeasurandValues.L3 = Utils.getRandomFloatRounded(maxAmperage); - } - currentMeasurandValues.allPhases = Utils.roundTo((currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / self.chargingStation.getNumberOfPhases(), 2); - } - break; - case CurrentOutType.DC: - maxAmperage = DCElectricUtils.amperage(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider, self.chargingStation.getVoltageOut()); - if (Utils.isUndefined(meterValuesTemplate[index].value)) { - currentMeasurandValues.allPhases = Utils.getRandomFloatRounded(maxAmperage); - } - break; - default: - logger.error(errMsg); - throw Error(errMsg); - } - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], currentMeasurandValues.allPhases)); - const sampledValuesIndex = meterValue.sampledValue.length - 1; - if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxAmperage || debug) { - logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxAmperage}`); + const connector = self.chargingStation.getConnector(connectorId); + // SoC measurand + const socSampledValueTemplate = self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.STATE_OF_CHARGE); + if (socSampledValueTemplate) { + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, Utils.getRandomInt(100))); + const sampledValuesIndex = meterValue.sampledValue.length - 1; + if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) { + logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/100`); + } + } + // Voltage measurand + const voltageSampledValueTemplate = self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.VOLTAGE); + if (voltageSampledValueTemplate) { + const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value ? parseInt(voltageSampledValueTemplate.value) : self.chargingStation.getVoltageOut(); + const fluctuationPercent = voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; + const voltageMeasurandValue = Utils.getRandomFloatFluctuatedRounded(voltageSampledValueTemplateValue, fluctuationPercent); + if (self.chargingStation.getNumberOfPhases() !== 3 || (self.chargingStation.getNumberOfPhases() === 3 && self.chargingStation.getMainVoltageMeterValues())) { + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue)); + } + const defaultVoltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(VoltageOut.VOLTAGE_400, fluctuationPercent); + for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) { + const phaseLineToNeutralValue = `L${phase}-N`; + const voltagePhaseLineToNeutralSampledValueTemplate = self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.VOLTAGE, + phaseLineToNeutralValue as OCPP16MeterValuePhase); + let voltagePhaseLineToNeutralMeasurandValue: number; + if (voltagePhaseLineToNeutralSampledValueTemplate) { + const voltagePhaseLineToNeutralSampledValueTemplateValue = voltagePhaseLineToNeutralSampledValueTemplate.value ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) : self.chargingStation.getVoltageOut(); + const fluctuationPhaseToNeutralPercent = voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; + voltagePhaseLineToNeutralMeasurandValue = Utils.getRandomFloatFluctuatedRounded(voltagePhaseLineToNeutralSampledValueTemplateValue, fluctuationPhaseToNeutralPercent); } - for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) { - const phaseValue = `L${phase}`; - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], currentMeasurandValues[phaseValue], null, - phaseValue as MeterValuePhase)); + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate, + voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue, null, phaseLineToNeutralValue as OCPP16MeterValuePhase)); + const phaseLineToLineValue = `L${phase}-L${(phase + 1) % self.chargingStation.getNumberOfPhases() !== 0 ? (phase + 1) % self.chargingStation.getNumberOfPhases() : self.chargingStation.getNumberOfPhases()}`; + const voltagePhaseLineToLineSampledValueTemplate = self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.VOLTAGE, phaseLineToLineValue as OCPP16MeterValuePhase); + let voltagePhaseLineToLineMeasurandValue: number; + if (voltagePhaseLineToLineSampledValueTemplate) { + const voltagePhaseLineToLineSampledValueTemplateValue = voltagePhaseLineToLineSampledValueTemplate.value ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) : VoltageOut.VOLTAGE_400; + const fluctuationPhaseLineToLinePercent = voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; + voltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(voltagePhaseLineToLineSampledValueTemplateValue, fluctuationPhaseLineToLinePercent); } - // Energy.Active.Import.Register measurand (default) - } else if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) { - OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand); - const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; - if (Utils.isUndefined(meterValuesTemplate[index].value)) { - const energyMeasurandValue = Utils.getRandomInt(self.chargingStation.stationInfo.maxPower / (self.chargingStation.stationInfo.powerDivider * 3600000) * interval); - // Persist previous value in connector - if (connector && !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) && connector.energyActiveImportRegisterValue >= 0 && - !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) && connector.transactionEnergyActiveImportRegisterValue >= 0) { - connector.energyActiveImportRegisterValue += energyMeasurandValue; - connector.transactionEnergyActiveImportRegisterValue += energyMeasurandValue; + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate, + voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue, null, phaseLineToLineValue as OCPP16MeterValuePhase)); + } + } + // Power.Active.Import measurand + const powerSampledValueTemplate = self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT); + let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; + if (self.chargingStation.getNumberOfPhases() === 3) { + powerPerPhaseSampledValueTemplates = { + L1: self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, OCPP16MeterValuePhase.L1_N), + L2: self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, OCPP16MeterValuePhase.L2_N), + L3: self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, OCPP16MeterValuePhase.L3_N), + }; + } + if (powerSampledValueTemplate) { + OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, powerSampledValueTemplate.measurand); + const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${powerSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${powerSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`; + const powerMeasurandValues = {} as MeasurandValues; + const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; + const maxPower = Math.round(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider); + const maxPowerPerPhase = Math.round((self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider) / self.chargingStation.getNumberOfPhases()); + switch (self.chargingStation.getCurrentOutType()) { + case CurrentOutType.AC: + if (self.chargingStation.getNumberOfPhases() === 3) { + const defaultFluctuatedPowerPerPhase = powerSampledValueTemplate.value + && Utils.getRandomFloatFluctuatedRounded(parseInt(powerSampledValueTemplate.value) / self.chargingStation.getNumberOfPhases(), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT); + const phase1FluctuatedValue = powerPerPhaseSampledValueTemplates?.L1?.value + && Utils.getRandomFloatFluctuatedRounded(parseInt(powerPerPhaseSampledValueTemplates.L1.value), powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT); + const phase2FluctuatedValue = powerPerPhaseSampledValueTemplates?.L2?.value + && Utils.getRandomFloatFluctuatedRounded(parseInt(powerPerPhaseSampledValueTemplates.L2.value), powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT); + const phase3FluctuatedValue = powerPerPhaseSampledValueTemplates?.L3?.value + && Utils.getRandomFloatFluctuatedRounded(parseInt(powerPerPhaseSampledValueTemplates.L3.value), powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT); + powerMeasurandValues.L1 = (phase1FluctuatedValue ?? defaultFluctuatedPowerPerPhase) ?? Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); + powerMeasurandValues.L2 = (phase2FluctuatedValue ?? defaultFluctuatedPowerPerPhase) ?? Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); + powerMeasurandValues.L3 = (phase3FluctuatedValue ?? defaultFluctuatedPowerPerPhase) ?? Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); } else { - connector.energyActiveImportRegisterValue = 0; - connector.transactionEnergyActiveImportRegisterValue = 0; + powerMeasurandValues.L1 = powerSampledValueTemplate.value + ? Utils.getRandomFloatFluctuatedRounded(parseInt(powerSampledValueTemplate.value), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT) + : Utils.getRandomFloatRounded(maxPower / unitDivider); + powerMeasurandValues.L2 = 0; + powerMeasurandValues.L3 = 0; } + powerMeasurandValues.allPhases = Utils.roundTo(powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, 2); + break; + case CurrentOutType.DC: + powerMeasurandValues.allPhases = powerSampledValueTemplate.value + ? Utils.getRandomFloatFluctuatedRounded(parseInt(powerSampledValueTemplate.value), powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT) + : Utils.getRandomFloatRounded(maxPower / unitDivider); + break; + default: + logger.error(errMsg); + throw Error(errMsg); + } + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(powerSampledValueTemplate, powerMeasurandValues.allPhases)); + const sampledValuesIndex = meterValue.sampledValue.length - 1; + const maxPowerRounded = Utils.roundTo(maxPower / unitDivider, 2); + if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxPowerRounded || debug) { + logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxPowerRounded}`); + } + for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) { + const phaseValue = `L${phase}-N`; + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(powerPerPhaseSampledValueTemplates[`L${phase}`] ?? powerSampledValueTemplate, powerMeasurandValues[`L${phase}`], null, + phaseValue as OCPP16MeterValuePhase)); + const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; + const maxPowerPerPhaseRounded = Utils.roundTo(maxPowerPerPhase / unitDivider, 2); + if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > maxPowerPerPhaseRounded || debug) { + logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: phase: ${meterValue.sampledValue[sampledValuesPerPhaseIndex].phase}, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesPerPhaseIndex].value}/${maxPowerPerPhaseRounded}`); } - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], - Utils.roundTo(self.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider, 4))); - const sampledValuesIndex = meterValue.sampledValue.length - 1; - const maxEnergy = Math.round(self.chargingStation.stationInfo.maxPower * 3600 / (self.chargingStation.stationInfo.powerDivider * interval)); - const maxEnergyRounded = Utils.roundTo(maxEnergy / unitDivider, 4); - if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxEnergyRounded || debug) { - logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxEnergyRounded}`); + } + } + // Current.Import measurand + const currentSampledValueTemplate = self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.CURRENT_IMPORT); + let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; + if (self.chargingStation.getNumberOfPhases() === 3) { + currentPerPhaseSampledValueTemplates = { + L1: self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.CURRENT_IMPORT, OCPP16MeterValuePhase.L1), + L2: self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.CURRENT_IMPORT, OCPP16MeterValuePhase.L2), + L3: self.chargingStation.getSampledValueTemplate(connectorId, OCPP16MeterValueMeasurand.CURRENT_IMPORT, OCPP16MeterValuePhase.L3), + }; + } + if (currentSampledValueTemplate) { + OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, currentSampledValueTemplate.measurand); + const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${currentSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${currentSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`; + const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; + let maxAmperage: number; + switch (self.chargingStation.getCurrentOutType()) { + case CurrentOutType.AC: + maxAmperage = ACElectricUtils.amperagePerPhaseFromPower(self.chargingStation.getNumberOfPhases(), self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider, self.chargingStation.getVoltageOut()); + if (self.chargingStation.getNumberOfPhases() === 3) { + const defaultFluctuatedAmperagePerPhase = currentSampledValueTemplate.value + && Utils.getRandomFloatFluctuatedRounded(parseInt(currentSampledValueTemplate.value), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT); + const phase1FluctuatedValue = currentPerPhaseSampledValueTemplates?.L1?.value + && Utils.getRandomFloatFluctuatedRounded(parseInt(currentPerPhaseSampledValueTemplates.L1.value), currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT); + const phase2FluctuatedValue = currentPerPhaseSampledValueTemplates?.L2?.value + && Utils.getRandomFloatFluctuatedRounded(parseInt(currentPerPhaseSampledValueTemplates.L2.value), currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT); + const phase3FluctuatedValue = currentPerPhaseSampledValueTemplates?.L3?.value + && Utils.getRandomFloatFluctuatedRounded(parseInt(currentPerPhaseSampledValueTemplates.L3.value), currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT); + currentMeasurandValues.L1 = (phase1FluctuatedValue ?? defaultFluctuatedAmperagePerPhase) ?? Utils.getRandomFloatRounded(maxAmperage); + currentMeasurandValues.L2 = (phase2FluctuatedValue ?? defaultFluctuatedAmperagePerPhase) ?? Utils.getRandomFloatRounded(maxAmperage); + currentMeasurandValues.L3 = (phase3FluctuatedValue ?? defaultFluctuatedAmperagePerPhase) ?? Utils.getRandomFloatRounded(maxAmperage); + } else { + currentMeasurandValues.L1 = currentSampledValueTemplate.value + ? Utils.getRandomFloatFluctuatedRounded(parseInt(currentSampledValueTemplate.value), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT) + : Utils.getRandomFloatRounded(maxAmperage); + currentMeasurandValues.L2 = 0; + currentMeasurandValues.L3 = 0; + } + currentMeasurandValues.allPhases = Utils.roundTo((currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / self.chargingStation.getNumberOfPhases(), 2); + break; + case CurrentOutType.DC: + maxAmperage = DCElectricUtils.amperage(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider, self.chargingStation.getVoltageOut()); + currentMeasurandValues.allPhases = currentSampledValueTemplate.value + ? Utils.getRandomFloatFluctuatedRounded(parseInt(currentSampledValueTemplate.value), currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT) + : Utils.getRandomFloatRounded(maxAmperage); + break; + default: + logger.error(errMsg); + throw Error(errMsg); + } + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(currentSampledValueTemplate, currentMeasurandValues.allPhases)); + const sampledValuesIndex = meterValue.sampledValue.length - 1; + if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxAmperage || debug) { + logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxAmperage}`); + } + for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) { + const phaseValue = `L${phase}`; + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(currentPerPhaseSampledValueTemplates[phaseValue] ?? currentSampledValueTemplate, + currentMeasurandValues[phaseValue], null, phaseValue as OCPP16MeterValuePhase)); + const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; + if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > maxAmperage || debug) { + logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: phase: ${meterValue.sampledValue[sampledValuesPerPhaseIndex].phase}, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesPerPhaseIndex].value}/${maxAmperage}`); } - // Unsupported measurand + } + } + // Energy.Active.Import.Register measurand (default) + const energySampledValueTemplate = self.chargingStation.getSampledValueTemplate(connectorId); + if (energySampledValueTemplate) { + OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, energySampledValueTemplate.measurand); + const unitDivider = energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; + const energyMeasurandValue = energySampledValueTemplate.value + // Cumulate the fluctuated value around the static one + ? Utils.getRandomFloatFluctuatedRounded(parseInt(energySampledValueTemplate.value), energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT) + : Utils.getRandomInt(self.chargingStation.stationInfo.maxPower / (self.chargingStation.stationInfo.powerDivider * 3600000) * interval); + // Persist previous value on connector + if (connector && !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) && connector.energyActiveImportRegisterValue >= 0 && + !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) && connector.transactionEnergyActiveImportRegisterValue >= 0) { + connector.energyActiveImportRegisterValue += energyMeasurandValue; + connector.transactionEnergyActiveImportRegisterValue += energyMeasurandValue; } else { - logger.info(`${self.chargingStation.logPrefix()} Unsupported MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} on connectorId ${connectorId}`); + connector.energyActiveImportRegisterValue = 0; + connector.transactionEnergyActiveImportRegisterValue = 0; + } + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(energySampledValueTemplate, + Utils.roundTo(self.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider, 4))); + const sampledValuesIndex = meterValue.sampledValue.length - 1; + const maxEnergy = Math.round(self.chargingStation.stationInfo.maxPower * 3600 / (self.chargingStation.stationInfo.powerDivider * interval)); + const maxEnergyRounded = Utils.roundTo(maxEnergy / unitDivider, 4); + if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxEnergyRounded || debug) { + logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxEnergyRounded}`); } } const payload: MeterValuesRequest = { diff --git a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts index 65b599bf..b50ea9f7 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts @@ -1,6 +1,7 @@ -import { MeterValueContext, MeterValueLocation, MeterValuePhase, MeterValueUnit, OCPP16MeterValue, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues'; +import { MeterValueContext, MeterValueLocation, MeterValueUnit, OCPP16MeterValue, OCPP16MeterValueMeasurand, OCPP16MeterValuePhase, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues'; import ChargingStation from '../../ChargingStation'; +import { SampledValueTemplate } from '../../../types/Connectors'; import Utils from '../../../utils/Utils'; import logger from '../../../utils/Logger'; @@ -17,7 +18,8 @@ export class OCPP16ServiceUtils { } } - public static buildSampledValue(sampledValueTemplate: OCPP16SampledValue, value: number, context?: MeterValueContext, phase?: MeterValuePhase): OCPP16SampledValue { + public static buildSampledValue(sampledValueTemplate: SampledValueTemplate, value: number, context?: MeterValueContext, phase?: OCPP16MeterValuePhase): OCPP16SampledValue { + const sampledValueValue = value ?? (sampledValueTemplate.value ?? null); const sampledValueContext = context ?? (sampledValueTemplate.context ?? null); const sampledValueLocation = sampledValueTemplate.location ? sampledValueTemplate.location @@ -28,7 +30,7 @@ export class OCPP16ServiceUtils { ...!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }, ...!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && { measurand: sampledValueTemplate.measurand }, ...!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }, - ...!Utils.isNullOrUndefined(sampledValueTemplate.value) ? { value: sampledValueTemplate.value } : { value: value.toString() }, + ...!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }, ...!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }, }; } @@ -65,15 +67,10 @@ export class OCPP16ServiceUtils { timestamp: new Date().toISOString(), sampledValue: [], }; - const meterValuesTemplate: OCPP16SampledValue[] = chargingStation.getConnector(connectorId).MeterValues; - for (let index = 0; index < meterValuesTemplate.length; index++) { - // Energy.Active.Import.Register measurand (default) - if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) { - const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], - Utils.roundTo(meterBegin / unitDivider, 4), MeterValueContext.TRANSACTION_BEGIN)); - } - } + // Energy.Active.Import.Register measurand (default) + const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); + const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(sampledValueTemplate, Utils.roundTo(meterBegin / unitDivider, 4), MeterValueContext.TRANSACTION_BEGIN)); return meterValue; } @@ -82,14 +79,10 @@ export class OCPP16ServiceUtils { timestamp: new Date().toISOString(), sampledValue: [], }; - const meterValuesTemplate: OCPP16SampledValue[] = chargingStation.getConnector(connectorId).MeterValues; - for (let index = 0; index < meterValuesTemplate.length; index++) { - // Energy.Active.Import.Register measurand (default) - if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) { - const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; - meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], Utils.roundTo(meterEnd / unitDivider, 4), MeterValueContext.TRANSACTION_END)); - } - } + // Energy.Active.Import.Register measurand (default) + const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); + const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; + meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(sampledValueTemplate, Utils.roundTo(meterEnd / unitDivider, 4), MeterValueContext.TRANSACTION_END)); return meterValue; } diff --git a/src/types/ChargingStationTemplate.ts b/src/types/ChargingStationTemplate.ts index dc7fc73e..3b08cdae 100644 --- a/src/types/ChargingStationTemplate.ts +++ b/src/types/ChargingStationTemplate.ts @@ -54,7 +54,6 @@ export default interface ChargingStationTemplate { useConnectorId0?: boolean; randomConnectors?: boolean; resetTime?: number; - connectionTimeout?: number; autoReconnectMaxRetries?: number; reconnectExponentialDelay?: boolean; registrationMaxRetries?: number; @@ -63,6 +62,7 @@ export default interface ChargingStationTemplate { outOfOrderEndMeterValues?: boolean; meteringPerTransaction?: boolean; transactionDataMeterValues?: boolean; + mainVoltageMeterValues?: boolean; Configuration?: ChargingStationConfiguration; AutomaticTransactionGenerator: AutomaticTransactionGenerator; Connectors: Connectors; diff --git a/src/types/ConfigurationData.ts b/src/types/ConfigurationData.ts index 6d67a06e..91437807 100644 --- a/src/types/ConfigurationData.ts +++ b/src/types/ConfigurationData.ts @@ -10,7 +10,6 @@ export default interface ConfigurationData { supervisionURLs?: string[]; stationTemplateURLs: StationTemplateURL[]; statisticsDisplayInterval?: number; - connectionTimeout?: number; autoReconnectMaxRetries?: number; distributeStationsToTenantsEqually?: boolean; workerProcess?: WorkerProcessType; diff --git a/src/types/Connectors.ts b/src/types/Connectors.ts index 85ff64c2..44bdb917 100644 --- a/src/types/Connectors.ts +++ b/src/types/Connectors.ts @@ -4,11 +4,15 @@ import { AvailabilityType } from './ocpp/Requests'; import { ChargePointStatus } from './ocpp/ChargePointStatus'; import { ChargingProfile } from './ocpp/ChargingProfile'; +export interface SampledValueTemplate extends SampledValue { + fluctuationPercent?: number; +} + export interface Connector { availability: AvailabilityType; bootStatus?: ChargePointStatus; status?: ChargePointStatus; - MeterValues: SampledValue[]; + MeterValues: SampledValueTemplate[]; transactionStarted?: boolean; transactionId?: number; transactionSetInterval?: NodeJS.Timeout; diff --git a/src/types/MeasurandPerPhaseSampledValueTemplates.ts b/src/types/MeasurandPerPhaseSampledValueTemplates.ts new file mode 100644 index 00000000..c78d9ebd --- /dev/null +++ b/src/types/MeasurandPerPhaseSampledValueTemplates.ts @@ -0,0 +1,7 @@ +import { SampledValueTemplate } from './Connectors'; + +export default interface MeasurandPerPhaseSampledValueTemplates { + L1?: SampledValueTemplate; + L2?: SampledValueTemplate; + L3?: SampledValueTemplate; +} diff --git a/src/types/ocpp/1.6/MeterValues.ts b/src/types/ocpp/1.6/MeterValues.ts index e93746a4..2f069538 100644 --- a/src/types/ocpp/1.6/MeterValues.ts +++ b/src/types/ocpp/1.6/MeterValues.ts @@ -61,7 +61,7 @@ export enum MeterValueLocation { OUTLET = 'Outlet' } -export enum MeterValuePhase { +export enum OCPP16MeterValuePhase { L1 = 'L1', L2 = 'L2', L3 = 'L3', @@ -84,7 +84,7 @@ export interface OCPP16SampledValue { unit?: MeterValueUnit; context?: MeterValueContext; measurand?: OCPP16MeterValueMeasurand; - phase?: MeterValuePhase; + phase?: OCPP16MeterValuePhase; location?: MeterValueLocation; format?: MeterValueFormat; } diff --git a/src/types/ocpp/1.6/Responses.ts b/src/types/ocpp/1.6/Responses.ts index f40cc88b..317926f3 100644 --- a/src/types/ocpp/1.6/Responses.ts +++ b/src/types/ocpp/1.6/Responses.ts @@ -4,26 +4,17 @@ export interface HeartbeatResponse { currentTime: string; } -export enum DefaultStatus { - ACCEPTED = 'Accepted', - REJECTED = 'Rejected' -} - -export interface DefaultResponse { - status: DefaultStatus; -} - -export enum UnlockStatus { +export enum OCPP16UnlockStatus { UNLOCKED = 'Unlocked', UNLOCK_FAILED = 'UnlockFailed', NOT_SUPPORTED = 'NotSupported' } export interface UnlockConnectorResponse { - status: UnlockStatus; + status: OCPP16UnlockStatus; } -export enum ConfigurationStatus { +export enum OCPP16ConfigurationStatus { ACCEPTED = 'Accepted', REJECTED = 'Rejected', REBOOT_REQUIRED = 'RebootRequired', @@ -31,7 +22,7 @@ export enum ConfigurationStatus { } export interface ChangeConfigurationResponse { - status: ConfigurationStatus; + status: OCPP16ConfigurationStatus; } export enum OCPP16RegistrationStatus { @@ -47,38 +38,38 @@ export interface OCPP16BootNotificationResponse { } // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface StatusNotificationResponse { } +export interface StatusNotificationResponse {} export interface GetConfigurationResponse { configurationKey: OCPPConfigurationKey[]; unknownKey: string[]; } -export enum ChargingProfileStatus { +export enum OCPP16ChargingProfileStatus { ACCEPTED = 'Accepted', REJECTED = 'Rejected', NOT_SUPPORTED = 'NotSupported', } export interface SetChargingProfileResponse { - status: ChargingProfileStatus; + status: OCPP16ChargingProfileStatus; } -export enum AvailabilityStatus { +export enum OCPP16AvailabilityStatus { ACCEPTED = 'Accepted', REJECTED = 'Rejected', SCHEDULED = 'Scheduled' } export interface ChangeAvailabilityResponse { - status: AvailabilityStatus; + status: OCPP16AvailabilityStatus; } -export enum ClearChargingProfileStatus { +export enum OCPP16ClearChargingProfileStatus { ACCEPTED = 'Accepted', UNKNOWN = 'Unknown' } export interface ClearChargingProfileResponse { - status: ClearChargingProfileStatus; + status: OCPP16ClearChargingProfileStatus; } diff --git a/src/types/ocpp/MeterValues.ts b/src/types/ocpp/MeterValues.ts index 1022c9fb..f2e6e9c0 100644 --- a/src/types/ocpp/MeterValues.ts +++ b/src/types/ocpp/MeterValues.ts @@ -1,4 +1,4 @@ -import { OCPP16MeterValue, OCPP16MeterValueMeasurand, OCPP16SampledValue } from './1.6/MeterValues'; +import { OCPP16MeterValue, OCPP16MeterValueMeasurand, OCPP16MeterValuePhase, OCPP16SampledValue } from './1.6/MeterValues'; export type MeterValueMeasurand = OCPP16MeterValueMeasurand; @@ -6,6 +6,12 @@ export const MeterValueMeasurand = { ...OCPP16MeterValueMeasurand }; +export type MeterValuePhase = OCPP16MeterValuePhase; + +export const MeterValuePhase = { + ...OCPP16MeterValuePhase +}; + export type SampledValue = OCPP16SampledValue; export type MeterValue = OCPP16MeterValue; diff --git a/src/types/ocpp/Responses.ts b/src/types/ocpp/Responses.ts index d134f545..a09da33b 100644 --- a/src/types/ocpp/Responses.ts +++ b/src/types/ocpp/Responses.ts @@ -1,9 +1,48 @@ -import { OCPP16BootNotificationResponse, OCPP16RegistrationStatus } from './1.6/Responses'; +import { OCPP16AvailabilityStatus, OCPP16BootNotificationResponse, OCPP16ChargingProfileStatus, OCPP16ClearChargingProfileStatus, OCPP16ConfigurationStatus, OCPP16RegistrationStatus, OCPP16UnlockStatus } from './1.6/Responses'; export type BootNotificationResponse = OCPP16BootNotificationResponse; +export enum DefaultStatus { + ACCEPTED = 'Accepted', + REJECTED = 'Rejected' +} + +export interface DefaultResponse { + status: DefaultStatus; +} + export type RegistrationStatus = OCPP16RegistrationStatus; export const RegistrationStatus = { ...OCPP16RegistrationStatus }; + +export type AvailabilityStatus = OCPP16AvailabilityStatus; + +export const AvailabilityStatus = { + ...OCPP16AvailabilityStatus +}; + +export type ChargingProfileStatus = OCPP16ChargingProfileStatus; + +export const ChargingProfileStatus = { + ...OCPP16ChargingProfileStatus +}; + +export type ClearChargingProfileStatus = OCPP16ClearChargingProfileStatus; + +export const ClearChargingProfileStatus = { + ...OCPP16ClearChargingProfileStatus +}; + +export type ConfigurationStatus = OCPP16ConfigurationStatus; + +export const ConfigurationStatus = { + ...OCPP16ConfigurationStatus +}; + +export type UnlockStatus = OCPP16UnlockStatus; + +export const UnlockStatus = { + ...OCPP16UnlockStatus +}; diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index 4b436c47..a65e6c99 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -21,16 +21,9 @@ export default class Configuration { return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'statisticsDisplayInterval') ? Configuration.getConfig().statisticsDisplayInterval : 60; } - static getConnectionTimeout(): number { + static getAutoReconnectMaxRetries(): number { Configuration.deprecateConfigurationKey('autoReconnectTimeout', 'Use \'ConnectionTimeOut\' OCPP parameter in charging station template instead'); Configuration.deprecateConfigurationKey('connectionTimeout', 'Use \'ConnectionTimeOut\' OCPP parameter in charging station template instead'); - // Read conf - if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'connectionTimeout')) { - return Configuration.getConfig().connectionTimeout; - } - } - - static getAutoReconnectMaxRetries(): number { Configuration.deprecateConfigurationKey('autoReconnectMaxRetries', 'Use it in charging station template instead'); // Read conf if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'autoReconnectMaxRetries')) { diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index a7e274a7..350170aa 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -1,4 +1,6 @@ -import { AvailabilityStatus, ChargingProfileStatus, ClearChargingProfileStatus, ConfigurationStatus, DefaultStatus, UnlockStatus } from '../types/ocpp/1.6/Responses'; +import { AvailabilityStatus, ChargingProfileStatus, ClearChargingProfileStatus, ConfigurationStatus, DefaultStatus, UnlockStatus } from '../types/ocpp/Responses'; + +import { MeterValueMeasurand } from '../types/ocpp/MeterValues'; export default class Constants { static readonly ENTITY_CHARGING_STATION = 'ChargingStation'; @@ -38,4 +40,9 @@ export default class Constants { static readonly DEFAULT_CHARGING_STATIONS_PER_WORKER = 1; static readonly DEFAULT_CONNECTION_TIMEOUT = 30; + + static readonly SUPPORTED_MEASURANDS = Object.freeze([MeterValueMeasurand.STATE_OF_CHARGE, MeterValueMeasurand.VOLTAGE, + MeterValueMeasurand.POWER_ACTIVE_IMPORT, MeterValueMeasurand.CURRENT_IMPORT, MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER]); + + static readonly DEFAULT_FLUCTUATION_PERCENT = 5; } diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 293b7184..5184bce7 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -125,6 +125,13 @@ export default class Utils { return Utils.roundTo(Utils.getRandomFloat(max), scale); } + static getRandomFloatFluctuatedRounded(staticValue: number, fluctuationPercent: number, scale = 2): number { + if (fluctuationPercent === 0) { + return Utils.roundTo(staticValue, scale); + } + return Utils.getRandomFloatRounded(staticValue + staticValue * (fluctuationPercent / 100), staticValue - staticValue * (fluctuationPercent / 100), scale); + } + static cloneObject(object: T): T { return JSON.parse(JSON.stringify(object)) as T; } -- 2.34.1