Add per phase support to MeterValues in template.
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 19 Jun 2021 19:47:35 +0000 (21:47 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 19 Jun 2021 19:47:35 +0000 (21:47 +0200)
+ Add support for static value + fluctuation
+ Add support for line to line voltage

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
18 files changed:
README.md
package-lock.json
package.json
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/1.6/OCCP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/types/ChargingStationTemplate.ts
src/types/ConfigurationData.ts
src/types/Connectors.ts
src/types/MeasurandPerPhaseSampledValueTemplates.ts [new file with mode: 0644]
src/types/ocpp/1.6/MeterValues.ts
src/types/ocpp/1.6/Responses.ts
src/types/ocpp/MeterValues.ts
src/types/ocpp/Responses.ts
src/utils/Configuration.ts
src/utils/Constants.ts
src/utils/Utils.ts

index db22f0e1a73dad0a6e57f8d79cfb40a53256d867..a16ad55b1527a3f1b61a725ac5140abceaf7e41c 100644 (file)
--- 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
index a7732ebb0249502c861b23447a57006956ed9331..929f04e8119192dfc31a7bdb1bc677c1e4c97b52 100644 (file)
       "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": "*"
       }
     },
     "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",
       }
     },
     "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",
       "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",
       }
     },
     "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": {
       },
       "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",
       }
     },
     "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": {
       }
     },
     "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",
index 1da708ac46a49f0a4b741217dea6c26cb6276575..c16520c18034b47e2f7477f1130caad532559aa5 100644 (file)
@@ -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",
     "@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",
     "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"
   }
 }
index ed9b79f07c9f7c419489d60130b1a75f5e5bb4e7..6bab3d86cc1f2958de511a60d1b5f0ea1a422221 100644 (file)
@@ -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;
   }
 
index 7c32cb7897065880d450255537fdbd5f86a68c60..0ef5fa0ceae1407c67c9fbe94cafd15b627eb278 100644 (file)
@@ -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';
index 2e8b540bbf1ca7c2f02e3910b18f4f99290e465b..5221d50767032c5ebaed5975531f8836372026f4 100644 (file)
@@ -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 = {
index 65b599bf767d2ff7c1611b005360a70beac379a8..b50ea9f7303adba864042f060038212fd5fb72d4 100644 (file)
@@ -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;
   }
 
index dc7fc73e2b868fc50e2d2bafb680bf2d08398166..3b08cdae693467e839973b77c021475578e7587e 100644 (file)
@@ -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;
index 6d67a06e741207fb670fa712e716e9aa662a33d7..91437807a629363c6df60260c4397fe1d4916726 100644 (file)
@@ -10,7 +10,6 @@ export default interface ConfigurationData {
   supervisionURLs?: string[];
   stationTemplateURLs: StationTemplateURL[];
   statisticsDisplayInterval?: number;
-  connectionTimeout?: number;
   autoReconnectMaxRetries?: number;
   distributeStationsToTenantsEqually?: boolean;
   workerProcess?: WorkerProcessType;
index 85ff64c208ed9c4d0f90934ad3b0959317ba6583..44bdb9175d68148be8bbc78e2434829888d90dce 100644 (file)
@@ -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 (file)
index 0000000..c78d9eb
--- /dev/null
@@ -0,0 +1,7 @@
+import { SampledValueTemplate } from './Connectors';
+
+export default interface MeasurandPerPhaseSampledValueTemplates {
+  L1?: SampledValueTemplate;
+  L2?: SampledValueTemplate;
+  L3?: SampledValueTemplate;
+}
index e93746a4c43860df37d0e8eb01d13adb2d18bac9..2f0695389e731c27847b3edd0074f5df00aa8c67 100644 (file)
@@ -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;
 }
index f40cc88ba00992d8110d98df3c3f16b168e74c55..317926f3fe921be2020dc59ac6c687cea0b7c1bc 100644 (file)
@@ -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;
 }
index 1022c9fbbce8a5d8c62a1f13b4f49f59e19a8d86..f2e6e9c05c11dc9c5b03166c1dfd49b6ac7b9f11 100644 (file)
@@ -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;
index d134f545de2e9118152f348fe634ae14c79af428..a09da33b0fe86af5e6fa9cdcdff120c8b1c71180 100644 (file)
@@ -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
+};
index 4b436c4793b7c77618b5e6df23c60a7e854ec16f..a65e6c995b999d3e62527fc7b5e09d98fd5b6b6b 100644 (file)
@@ -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')) {
index a7e274a7770c6cb8f466b79e7b9d91d8c9e559b9..350170aacd1e3ca94f8425f31aecc35b14dee9c1 100644 (file)
@@ -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;
 }
index 293b718485350d35f1b8dd06e7c97ab42ff795d7..5184bce796c7df04b14d3b48a684726e8e8c87e3 100644 (file)
@@ -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<T>(object: T): T {
     return JSON.parse(JSON.stringify(object)) as T;
   }