From 6ecb15e433167b2dae32d54bf7bed72c2d1369a6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 21 Oct 2020 11:54:25 +0200 Subject: [PATCH] Handle the number of connectors in metervalues generator. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit And add handling of the MeterValuesSampledData standard OCPP parameter. Signed-off-by: Jérôme Benoit --- .../abb.station-template.json | 6 ++ .../evlink.station-template.json | 5 ++ .../keba.station-template.json | 5 ++ .../schneider-imredd.station-template.json | 19 +++-- .../schneider.station-template.json | 6 ++ .../siemens.station-template.json | 5 ++ .../virtual-simple-atg.station-template.json | 5 ++ .../virtual-simple.station-template.json | 5 ++ .../virtual.station-template.json | 5 ++ src/charging-station/ChargingStation.js | 72 ++++++++++++++++--- src/index.js | 5 +- 11 files changed, 116 insertions(+), 22 deletions(-) diff --git a/src/assets/station-templates/abb.station-template.json b/src/assets/station-templates/abb.station-template.json index bef7dbb2..f0da9b4b 100644 --- a/src/assets/station-templates/abb.station-template.json +++ b/src/assets/station-templates/abb.station-template.json @@ -5,6 +5,7 @@ "chargePointVendor": "ABB", "firmwareVersion": "4.0.4.22", "power": 50000, + "powerSharedByConnectors": true, "powerUnit": "W", "numberOfConnectors": 2, "useConnectorId0": false, @@ -12,6 +13,11 @@ "resetTime": "30", "Configuration": { "configurationKey": [ + { + "key": "MeterValuesSampledData", + "readonly": false, + "value": "SoC,Energy.Active.Import.Register,Voltage" + }, { "key": "MeterValueSampleInterval", "readonly": false, diff --git a/src/assets/station-templates/evlink.station-template.json b/src/assets/station-templates/evlink.station-template.json index cf78e893..fee27e92 100644 --- a/src/assets/station-templates/evlink.station-template.json +++ b/src/assets/station-templates/evlink.station-template.json @@ -11,6 +11,11 @@ "randomConnectors": false, "Configuration": { "configurationKey": [ + { + "key": "MeterValuesSampledData", + "readonly": false, + "value": "Energy.Active.Import.Register" + }, { "key": "MeterValueSampleInterval", "readonly": false, diff --git a/src/assets/station-templates/keba.station-template.json b/src/assets/station-templates/keba.station-template.json index 4cadd5c6..5f217ebb 100644 --- a/src/assets/station-templates/keba.station-template.json +++ b/src/assets/station-templates/keba.station-template.json @@ -10,6 +10,11 @@ "randomConnectors": false, "Configuration": { "configurationKey": [ + { + "key": "MeterValuesSampledData", + "readonly": false, + "value": "Energy.Active.Import.Register" + }, { "key": "MeterValueSampleInterval", "readonly": false, diff --git a/src/assets/station-templates/schneider-imredd.station-template.json b/src/assets/station-templates/schneider-imredd.station-template.json index f383008e..c467d3be 100644 --- a/src/assets/station-templates/schneider-imredd.station-template.json +++ b/src/assets/station-templates/schneider-imredd.station-template.json @@ -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, @@ -49,14 +56,6 @@ "context": "Sample.Periodic" } ] - }, - "2": { - "MeterValues": [ - { - "unit": "Wh", - "context": "Sample.Periodic" - } - ] } } } diff --git a/src/assets/station-templates/schneider.station-template.json b/src/assets/station-templates/schneider.station-template.json index 225ce0e7..2961938e 100644 --- a/src/assets/station-templates/schneider.station-template.json +++ b/src/assets/station-templates/schneider.station-template.json @@ -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, diff --git a/src/assets/station-templates/siemens.station-template.json b/src/assets/station-templates/siemens.station-template.json index e362505e..e8e234a2 100644 --- a/src/assets/station-templates/siemens.station-template.json +++ b/src/assets/station-templates/siemens.station-template.json @@ -10,6 +10,11 @@ "randomConnectors": false, "Configuration": { "configurationKey": [ + { + "key": "MeterValuesSampledData", + "readonly": false, + "value": "Energy.Active.Import.Register" + }, { "key": "MeterValueSampleInterval", "readonly": false, diff --git a/src/assets/station-templates/virtual-simple-atg.station-template.json b/src/assets/station-templates/virtual-simple-atg.station-template.json index a17f6dd5..e5b2d3a5 100644 --- a/src/assets/station-templates/virtual-simple-atg.station-template.json +++ b/src/assets/station-templates/virtual-simple-atg.station-template.json @@ -9,6 +9,11 @@ "randomConnectors": false, "Configuration": { "configurationKey": [ + { + "key": "MeterValuesSampledData", + "readonly": false, + "value": "SoC,Energy.Active.Import.Register" + }, { "key": "MeterValueSampleInterval", "readonly": false, diff --git a/src/assets/station-templates/virtual-simple.station-template.json b/src/assets/station-templates/virtual-simple.station-template.json index 3e2898bd..c10fc3c5 100644 --- a/src/assets/station-templates/virtual-simple.station-template.json +++ b/src/assets/station-templates/virtual-simple.station-template.json @@ -9,6 +9,11 @@ "randomConnectors": false, "Configuration": { "configurationKey": [ + { + "key": "MeterValuesSampledData", + "readonly": false, + "value": "SoC,Energy.Active.Import.Register,Voltage" + }, { "key": "MeterValueSampleInterval", "readonly": false, diff --git a/src/assets/station-templates/virtual.station-template.json b/src/assets/station-templates/virtual.station-template.json index 62bf86c7..b0b2cd50 100644 --- a/src/assets/station-templates/virtual.station-template.json +++ b/src/assets/station-templates/virtual.station-template.json @@ -9,6 +9,11 @@ "randomConnectors": false, "Configuration": { "configurationKey": [ + { + "key": "MeterValuesSampledData", + "readonly": false, + "value": "SoC,Energy.Active.Import.Register,Voltage" + }, { "key": "MeterValueSampleInterval", "readonly": false, diff --git a/src/charging-station/ChargingStation.js b/src/charging-station/ChargingStation.js index 2c699be8..d5b1a692 100644 --- a/src/charging-station/ChargingStation.js +++ b/src/charging-station/ChargingStation.js @@ -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 { diff --git a/src/index.js b/src/index.js index d8ea9471..b87c4253 100644 --- a/src/index.js +++ b/src/index.js @@ -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, ' ')); -- 2.34.1