Handle the number of connectors in metervalues generator.
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 21 Oct 2020 09:54:25 +0000 (11:54 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 21 Oct 2020 09:54:25 +0000 (11:54 +0200)
And add handling of the MeterValuesSampledData standard OCPP parameter.

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/assets/station-templates/abb.station-template.json
src/assets/station-templates/evlink.station-template.json
src/assets/station-templates/keba.station-template.json
src/assets/station-templates/schneider-imredd.station-template.json
src/assets/station-templates/schneider.station-template.json
src/assets/station-templates/siemens.station-template.json
src/assets/station-templates/virtual-simple-atg.station-template.json
src/assets/station-templates/virtual-simple.station-template.json
src/assets/station-templates/virtual.station-template.json
src/charging-station/ChargingStation.js
src/index.js

index bef7dbb202dd6d7808dd226e186950bbed77ea1f..f0da9b4b7fb8933b275d5a9cc8d2a70f6d731571 100644 (file)
@@ -5,6 +5,7 @@
   "chargePointVendor": "ABB",
   "firmwareVersion": "4.0.4.22",
   "power": 50000,
+  "powerSharedByConnectors": true,
   "powerUnit": "W",
   "numberOfConnectors": 2,
   "useConnectorId0": false,
   "resetTime": "30",
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "SoC,Energy.Active.Import.Register,Voltage"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
index cf78e8939923ef7895b479976c177ebc42050b82..fee27e9269ae5bbfdd4f24ceaa57c2184efbd285 100644 (file)
   "randomConnectors": false,
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "Energy.Active.Import.Register"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
index 4cadd5c6894cf9a632cbe7e9a98903f47b763230..5f217ebba98988feabdbc3a2e32e0f8c588f5614 100644 (file)
   "randomConnectors": false,
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "Energy.Active.Import.Register"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
index f383008e35c3e9f577757f4ebfa15c1d24848ea4..c467d3be55d3a17ea08915447b8240c78d3cca05 100644 (file)
@@ -3,12 +3,19 @@
   "baseName": "CS-SCHNEIDER",
   "chargePointModel": "MONOBLOCK",
   "chargePointVendor": "Schneider Electric",
-  "power": 44160,
+  "chargePointSerialNumberPrefix": "EV.2S22P04",
+  "firmwareVersion": "3.3.0.10",
+  "power": 22080,
   "powerUnit": "W",
-  "numberOfConnectors": 2,
+  "numberOfConnectors": 1,
   "randomConnectors": false,
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "Energy.Active.Import.Register"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
           "context": "Sample.Periodic"
         }
       ]
-    },
-    "2": {
-      "MeterValues": [
-        {
-          "unit": "Wh",
-          "context": "Sample.Periodic"
-        }
-      ]
     }
   }
 }
index 225ce0e734793f8d36dbf6a2dc3da265f2a40e20..2961938ee61fa10869d2fe091bc2ac143d967904 100644 (file)
@@ -4,12 +4,18 @@
   "chargePointModel": "MONOBLOCK",
   "chargePointVendor": "Schneider Electric",
   "chargePointSerialNumberPrefix": "EV.2S22P44",
+  "firmwareVersion": "3.3.0.10",
   "power": 44160,
   "powerUnit": "W",
   "numberOfConnectors": 2,
   "randomConnectors": false,
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "Energy.Active.Import.Register"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
index e362505ee18ba9cd9117015dbdf586f0fa6547f2..e8e234a2ef02d952df2e2410cfa804175f35bf77 100644 (file)
   "randomConnectors": false,
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "Energy.Active.Import.Register"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
index a17f6dd51ae57f608f7a401e9d6aefec689d49c1..e5b2d3a5edfdc2a44a6dcbe9894efcd501c04f04 100644 (file)
@@ -9,6 +9,11 @@
   "randomConnectors": false,
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "SoC,Energy.Active.Import.Register"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
index 3e2898bd256298ed03cd070f1efcbd203045fc56..c10fc3c55085768fc0c6e75071623df7c05af479 100644 (file)
@@ -9,6 +9,11 @@
   "randomConnectors": false,
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "SoC,Energy.Active.Import.Register,Voltage"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
index 62bf86c7037eafedb363291e4b3927edf8132564..b0b2cd509405a8fd638e350a9904b7fc5f517eb2 100644 (file)
@@ -9,6 +9,11 @@
   "randomConnectors": false,
   "Configuration": {
     "configurationKey": [
+      {
+        "key": "MeterValuesSampledData",
+        "readonly": false,
+        "value": "SoC,Energy.Active.Import.Register,Voltage"
+      },
       {
         "key": "MeterValueSampleInterval",
         "readonly": false,
index 2c699be8618bd394ddfc6b66258f1506be3e7055..d5b1a692b2851dbb70e0e06cda7b38b3711dddfe 100644 (file)
@@ -65,7 +65,12 @@ class ChargingStation {
     this._supervisionUrl = this._getSupervisionURL();
     this._wsConnectionUrl = this._supervisionUrl + '/' + this._stationInfo.name;
     // Build connectors if needed
-    const maxConnectors = this._getMaxConnectors();
+    const maxConnectors = this._getMaxNumberOfConnectors();
+    if (maxConnectors <= 0) {
+      const errMsg = `${this._logPrefix()} Charging station template with no connectors`;
+      logger.error(errMsg);
+      throw Error(errMsg);
+    }
     const connectorsConfig = Utils.cloneJSonDocument(this._stationInfo.Connectors);
     const connectorsConfigHash = crypto.createHash('sha256').update(JSON.stringify(connectorsConfig) + maxConnectors.toString()).digest('hex');
     // FIXME: Handle shrinking the number of connectors
@@ -74,13 +79,16 @@ class ChargingStation {
       // Determine number of customized connectors
       let lastConnector;
       for (lastConnector in connectorsConfig) {
-        // Add connector 0, OCPP specification violation that for example KEBA have
+        // Add connector Id 0
         if (Utils.convertToInt(lastConnector) === 0 && Utils.convertToBoolean(this._stationInfo.useConnectorId0) &&
           connectorsConfig[lastConnector]) {
           this._connectors[lastConnector] = connectorsConfig[lastConnector];
         }
       }
       this._addConfigurationKey('NumberOfConnectors', maxConnectors, true);
+      if (!this._getConfigurationKey('MeterValuesSampledData')) {
+        this._addConfigurationKey('MeterValuesSampledData', 'Energy.Active.Import.Register');
+      }
       // Generate all connectors
       for (let index = 1; index <= maxConnectors; index++) {
         const randConnectorID = Utils.convertToBoolean(this._stationInfo.randomConnectors) ? Utils.getRandomInt(lastConnector, 1) : index;
@@ -93,6 +101,7 @@ class ChargingStation {
         this._initTransactionOnConnector(connector);
       }
     }
+    this._stationInfo.powerDivider = this._getPowerDivider();
     // FIXME: Conditionally initialize or use singleton design pattern per charging station
     this._statistics = new Statistics(this._stationInfo.name);
     this._performanceObserver = new PerformanceObserver((list) => {
@@ -141,14 +150,36 @@ class ChargingStation {
     return !Utils.isEmptyArray(this._authorizedTags);
   }
 
-  _getConnector(number) {
+  _getNumberOfRunningTransactions() {
+    let trxCount = 0;
+    for (const connector in this._connectors) {
+      if (this._getConnector(connector).transactionStarted) {
+        trxCount++;
+      }
+    }
+    return trxCount;
+  }
+
+  _getPowerDivider() {
+    let powerDivider = this._getNumberOfConnectors();
+    if (this._stationInfo.powerSharedByConnectors) {
+      powerDivider = this._getNumberOfRunningTransactions();
+    }
+    return powerDivider;
+  }
+
+  _getConnectorFromTemplate(number) {
     return this._stationInfo.Connectors[number];
   }
 
-  _getMaxConnectors() {
+  _getConnector(number) {
+    return this._connectors[number];
+  }
+
+  _getMaxNumberOfConnectors() {
     let maxConnectors = 0;
     if (!Utils.isEmptyArray(this._stationInfo.numberOfConnectors)) {
-      // Get evenly the number of connectors
+      // Distribute evenly the number of connectors
       maxConnectors = this._stationInfo.numberOfConnectors[(this._index - 1) % this._stationInfo.numberOfConnectors.length];
     } else {
       maxConnectors = this._stationInfo.numberOfConnectors;
@@ -156,6 +187,10 @@ class ChargingStation {
     return maxConnectors;
   }
 
+  _getNumberOfConnectors() {
+    return Utils.convertToBoolean(this._stationInfo.useConnectorId0) ? Object.keys(this._connectors).length - 1 : Object.keys(this._connectors).length;
+  }
+
   _getSupervisionURL() {
     const supervisionUrls = Utils.cloneJSonDocument(this._stationInfo.supervisionURL ? this._stationInfo.supervisionURL : Configuration.getSupervisionURLs());
     let indexUrl = 0;
@@ -257,10 +292,10 @@ class ChargingStation {
 
   async _startMeterValues(connectorId, interval) {
     if (!this._connectors[connectorId].transactionStarted) {
-      logger.error(`${this._logPrefix()} Trying to start MeterValues on connector ID ${connectorId} with no transaction started`);
+      logger.error(`${this._logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`);
       return;
     } else if (this._connectors[connectorId].transactionStarted && !this._connectors[connectorId].transactionId) {
-      logger.error(`${this._logPrefix()} Trying to start MeterValues on connector ID ${connectorId} with no transaction id`);
+      logger.error(`${this._logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction id`);
       return;
     }
     if (interval > 0) {
@@ -551,7 +586,7 @@ class ChargingStation {
       for (let index = 0; index < sampledValueLcl.sampledValue.length; index++) {
         const connector = self._connectors[connectorId];
         // SoC measurand
-        if (sampledValueLcl.sampledValue[index].measurand && sampledValueLcl.sampledValue[index].measurand === 'SoC') {
+        if (sampledValueLcl.sampledValue[index].measurand && sampledValueLcl.sampledValue[index].measurand === 'SoC' && self._getConfigurationKey('MeterValuesSampledData').value.includes('SoC')) {
           sampledValueLcl.sampledValue[index].value = !Utils.isUndefined(sampledValueLcl.sampledValue[index].value) ?
             sampledValueLcl.sampledValue[index].value :
             sampledValueLcl.sampledValue[index].value = Utils.getRandomInt(100);
@@ -559,12 +594,21 @@ class ChargingStation {
             logger.error(`${self._logPrefix()} MeterValues measurand ${sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValueLcl.sampledValue[index].value}`);
           }
         // Voltage measurand
-        } else if (sampledValueLcl.sampledValue[index].measurand && sampledValueLcl.sampledValue[index].measurand === 'Voltage') {
+        } else if (sampledValueLcl.sampledValue[index].measurand && sampledValueLcl.sampledValue[index].measurand === 'Voltage' && self._getConfigurationKey('MeterValuesSampledData').value.includes('Voltage')) {
           sampledValueLcl.sampledValue[index].value = !Utils.isUndefined(sampledValueLcl.sampledValue[index].value) ? sampledValueLcl.sampledValue[index].value : 230;
         // Energy.Active.Import.Register measurand (default)
         } else if (!sampledValueLcl.sampledValue[index].measurand || sampledValueLcl.sampledValue[index].measurand === 'Energy.Active.Import.Register') {
+          if (Utils.isUndefined(self._stationInfo.powerDivider)) {
+            const errMsg = `${self._logPrefix()} MeterValues measurand ${sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register'}: powerDivider is undefined`;
+            logger.error(errMsg);
+            throw Error(errMsg);
+          } else if (self._stationInfo.powerDivider && self._stationInfo.powerDivider <= 0) {
+            const errMsg = `${self._logPrefix()} MeterValues measurand ${sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register'}: powerDivider have zero or below value ${self._stationInfo.powerDivider}`;
+            logger.error(errMsg);
+            throw Error(errMsg);
+          }
           if (Utils.isUndefined(sampledValueLcl.sampledValue[index].value)) {
-            const measurandValue = Utils.getRandomInt(self._stationInfo.maxPower / 3600000 * interval);
+            const measurandValue = Utils.getRandomInt(self._stationInfo.maxPower / (self._stationInfo.powerDivider * 3600000) * interval);
             // Persist previous value in connector
             if (connector && connector.lastEnergyActiveImportRegisterValue >= 0) {
               connector.lastEnergyActiveImportRegisterValue += measurandValue;
@@ -574,7 +618,7 @@ class ChargingStation {
             sampledValueLcl.sampledValue[index].value = connector.lastEnergyActiveImportRegisterValue;
           }
           logger.info(`${self._logPrefix()} MeterValues measurand ${sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value ${sampledValueLcl.sampledValue[index].value}`);
-          const maxConsumption = self._stationInfo.maxPower * 3600 / interval;
+          const maxConsumption = self._stationInfo.maxPower * 3600 / (self._stationInfo.powerDivider * interval);
           if (sampledValueLcl.sampledValue[index].value > maxConsumption || debug) {
             logger.error(`${self._logPrefix()} MeterValues measurand ${sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValueLcl.sampledValue[index].value}/${maxConsumption}`);
           }
@@ -726,6 +770,9 @@ class ChargingStation {
       this._connectors[transactionConnectorId].lastEnergyActiveImportRegisterValue = 0;
       this.sendStatusNotification(requestPayload.connectorId, 'Charging');
       logger.info(this._logPrefix() + ' Transaction ' + payload.transactionId + ' STARTED on ' + this._stationInfo.name + '#' + requestPayload.connectorId + ' for idTag ' + requestPayload.idTag);
+      if (this._stationInfo.powerSharedByConnectors) {
+        this._stationInfo.powerDivider++;
+      }
       const configuredMeterValueSampleInterval = this._getConfigurationKey('MeterValueSampleInterval');
       this._startMeterValues(requestPayload.connectorId,
         configuredMeterValueSampleInterval ? configuredMeterValueSampleInterval.value * 1000 : 60000);
@@ -750,6 +797,9 @@ class ChargingStation {
     }
     if (payload.idTagInfo && payload.idTagInfo.status === 'Accepted') {
       this.sendStatusNotification(transactionConnectorId, 'Available');
+      if (this._stationInfo.powerSharedByConnectors) {
+        this._stationInfo.powerDivider--;
+      }
       logger.info(this._logPrefix() + ' Transaction ' + requestPayload.transactionId + ' STOPPED on ' + this._stationInfo.name + '#' + transactionConnectorId);
       this._resetTransactionOnConnector(transactionConnectorId);
     } else {
index d8ea947172d9dcdf268475b40a19176cb2782fbc..b87c42535d0804c0f6b59dd97a2c9e64f306063a 100644 (file)
@@ -7,9 +7,9 @@ class Bootstrap {
   static async start() {
     try {
       logger.debug('%s Configuration: %j', Utils.logPrefix(), Configuration.getConfig());
+      let numStationsTotal = 0;
       // Start each ChargingStation object in a worker thread
       if (Configuration.getStationTemplateURLs()) {
-        let numStationsTotal = 0;
         Configuration.getStationTemplateURLs().forEach((stationURL) => {
           try {
             const nbStation = stationURL.numberOfStation ? stationURL.numberOfStation : 0;
@@ -29,6 +29,9 @@ class Bootstrap {
       } else {
         console.log('No stationTemplateURLs defined in configuration, exiting');
       }
+      if (numStationsTotal === 0) {
+        console.log('No charging station template enabled in configuration, exiting');
+      }
     } catch (error) {
       // eslint-disable-next-line no-console
       console.log('Bootstrap start error ' + JSON.stringify(error, null, ' '));