X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.js;h=3e0f9bfcdfc73ab236e6ac7daf6a8609ef0875e6;hb=a07b4408ab12a39d84ec080c79ee06fe7a06bdeb;hp=97bca4a462948a560267957d89c8bea5f8fe8e14;hpb=3497da01c70a699484836f4d122a8cbaf670c44d;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.js b/src/charging-station/ChargingStation.js index 97bca4a4..3e0f9bfc 100644 --- a/src/charging-station/ChargingStation.js +++ b/src/charging-station/ChargingStation.js @@ -95,6 +95,9 @@ class ChargingStation { logger.debug(this._basicFormatLog() + ' Template file ' + this._stationTemplateFile + ' have changed, reload'); // Initialize this._initialize(); + this._addConfigurationKey('HeartBeatInterval', Utils.convertToInt(this._heartbeatInterval ? this._heartbeatInterval : 0)); + this._addConfigurationKey('HeartbeatInterval', Utils.convertToInt(this._heartbeatInterval ? this._heartbeatInterval : 0), false, false); + this._addConfigurationKey('NumberOfConnectors', this._getMaxConnectors(), true); } catch (error) { logger.error(this._basicFormatLog() + ' Charging station template file monitoring error: ' + error); } @@ -235,7 +238,6 @@ class ChargingStation { // Incoming Message case Constants.OCPP_JSON_CALL_MESSAGE: // Process the call - this._statistics.addMessage(commandName); await this.handleRequest(messageId, commandName, commandPayload); break; // Outcome Message @@ -253,7 +255,6 @@ class ChargingStation { throw new Error(`Response for unknown message id ${messageId}`); } delete this._requests[messageId]; - // this._statistics.addMessage(commandName) responseCallback(commandName, requestPayload); break; // Error Message @@ -330,11 +331,6 @@ class ChargingStation { } } - send(command, messageType = Constants.OCPP_JSON_CALL_MESSAGE) { - // Send Message - return this.sendMessage(Utils.generateUUID(), command, messageType); - } - sendError(messageId, err) { // Check exception: only OCPP error are accepted const error = err instanceof OCPPError ? err : new OCPPError(Constants.OCPP_ERROR_INTERNAL_ERROR, err.message); @@ -376,13 +372,14 @@ class ChargingStation { break; // Response case Constants.OCPP_JSON_CALL_RESULT_MESSAGE: + this._statistics.addMessage(commandName); // Build response messageToSend = JSON.stringify([messageType, messageId, command]); break; // Error Message case Constants.OCPP_JSON_CALL_ERROR_MESSAGE: // Build Message - this._statistics.addMessage(`Error ${command.code}`); + this._statistics.addMessage(`Error ${command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR} on ${commandName || ''}`); messageToSend = JSON.stringify([messageType, messageId, command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR, command.message ? command.message : '', command.details ? command.details : {}]); break; } @@ -419,6 +416,7 @@ class ChargingStation { // Function that will receive the request's rejection function rejectCallback(reason) { + self._statistics.addMessage(`Error ${command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR} on ${commandName || ''}`, true); // Build Exception // eslint-disable-next-line no-empty-function self._requests[messageId] = [() => { }, () => { }, '']; // Properly format the request @@ -441,17 +439,11 @@ class ChargingStation { for (lastConnector in connectorsConfig) { // Add connector 0, OCPP specification violation that for example KEBA have if (Utils.convertToInt(lastConnector) === 0 && Utils.convertToBoolean(this._stationInfo.useConnectorId0) && - connectorsConfig[lastConnector]) { + connectorsConfig[lastConnector]) { this._connectors[lastConnector] = connectorsConfig[lastConnector]; } } - let maxConnectors = 0; - if (Array.isArray(this._stationInfo.numberOfConnectors)) { - // Generate some connectors - maxConnectors = this._stationInfo.numberOfConnectors[(this._index - 1) % this._stationInfo.numberOfConnectors.length]; - } else { - maxConnectors = this._stationInfo.numberOfConnectors; - } + const maxConnectors = this._getMaxConnectors(); this._addConfigurationKey('NumberOfConnectors', maxConnectors, true); // Generate all connectors for (let index = 1; index <= maxConnectors; index++) { @@ -486,10 +478,11 @@ class ChargingStation { _resetTransactionOnConnector(connectorID) { this._connectors[connectorID].transactionStarted = false; this._connectors[connectorID].transactionId = null; + this._connectors[connectorID].idTag = null; this._connectors[connectorID].lastConsumptionValue = -1; this._connectors[connectorID].lastSoC = -1; - if (this._connectors[connectorID].transactionInterval) { - clearInterval(this._connectors[connectorID].transactionInterval); + if (this._connectors[connectorID].transactionSetInterval) { + clearInterval(this._connectors[connectorID].transactionSetInterval); } } @@ -505,41 +498,57 @@ class ChargingStation { } handleResponseStartTransaction(payload, requestPayload) { - // Set connector transaction related attributes - this._connectors[requestPayload.connectorId].transactionStarted = false; - this._connectors[requestPayload.connectorId].idTag = requestPayload.idTag; - - if (payload.idTagInfo.status === 'Accepted') { - for (const connector in this._connectors) { - if (Utils.convertToInt(connector) === Utils.convertToInt(requestPayload.connectorId)) { - this._connectors[connector].transactionStarted = true; - this._connectors[connector].transactionId = payload.transactionId; - this._connectors[connector].lastConsumptionValue = 0; - this._connectors[connector].lastSoC = 0; - logger.info(this._basicFormatLog() + ' Transaction ' + this._connectors[connector].transactionId + ' STARTED on ' + this._stationInfo.name + '#' + requestPayload.connectorId + ' for idTag ' + requestPayload.idTag); - this.sendStatusNotification(requestPayload.connectorId, 'Charging'); - const configuredMeterValueSampleInterval = this._getConfigurationKey('MeterValueSampleInterval'); - this.startMeterValues(requestPayload.connectorId, - configuredMeterValueSampleInterval ? configuredMeterValueSampleInterval.value * 1000 : 60000, - this); - } + if (this._connectors[requestPayload.connectorId].transactionStarted) { + logger.debug(this._basicFormatLog() + ' Try to start a transaction on an already used connector ' + requestPayload.connectorId + ' by transaction ' + this._connectors[requestPayload.connectorId].transactionId); + } + + let transactionConnectorId; + for (const connector in this._connectors) { + if (Utils.convertToInt(connector) === Utils.convertToInt(requestPayload.connectorId)) { + transactionConnectorId = connector; + break; } + } + if (!transactionConnectorId) { + logger.error(this._basicFormatLog() + ' Try to start a transaction on a non existing connector Id ' + requestPayload.connectorId); + return; + } + if (payload.idTagInfo && payload.idTagInfo.status === 'Accepted') { + this._connectors[transactionConnectorId].transactionStarted = true; + this._connectors[transactionConnectorId].transactionId = payload.transactionId; + this._connectors[transactionConnectorId].idTag = requestPayload.idTag; + this._connectors[transactionConnectorId].lastConsumptionValue = 0; + this._connectors[transactionConnectorId].lastSoC = 0; + this.sendStatusNotification(requestPayload.connectorId, 'Charging'); + logger.info(this._basicFormatLog() + ' Transaction ' + this._connectors[transactionConnectorId].transactionId + ' STARTED on ' + this._stationInfo.name + '#' + requestPayload.connectorId + ' for idTag ' + requestPayload.idTag); + const configuredMeterValueSampleInterval = this._getConfigurationKey('MeterValueSampleInterval'); + this.startMeterValues(requestPayload.connectorId, + configuredMeterValueSampleInterval ? configuredMeterValueSampleInterval.value * 1000 : 60000); } else { logger.error(this._basicFormatLog() + ' Starting transaction id ' + payload.transactionId + ' REJECTED with status ' + payload.idTagInfo.status + ', idTag ' + requestPayload.idTag); - for (const connector in this._connectors) { - if (Utils.convertToInt(connector) === Utils.convertToInt(requestPayload.connectorId)) { - this._resetTransactionOnConnector(connector); - } - } + this._resetTransactionOnConnector(transactionConnectorId); this.sendStatusNotification(requestPayload.connectorId, 'Available'); } } handleResponseStopTransaction(payload, requestPayload) { - if (payload.idTagInfo && payload.idTagInfo.status) { - logger.debug(this._basicFormatLog() + ' Stop transaction ' + requestPayload.transactionId + ' response status: ' + payload.idTagInfo.status); + let transactionConnectorId; + for (const connector in this._connectors) { + if (this._connectors[connector].transactionId === requestPayload.transactionId) { + transactionConnectorId = connector; + break; + } + } + if (!transactionConnectorId) { + logger.error(this._basicFormatLog() + ' Try to stop a non existing transaction ' + requestPayload.transactionId); + return; + } + if (payload.idTagInfo && payload.idTagInfo.status === 'Accepted') { + this.sendStatusNotification(transactionConnectorId, 'Available'); + logger.info(this._basicFormatLog() + ' Transaction ' + this._connectors[transactionConnectorId].transactionId + ' STOPPED on ' + this._stationInfo.name + '#' + transactionConnectorId); + this._resetTransactionOnConnector(transactionConnectorId); } else { - logger.debug(this._basicFormatLog() + ' Stop transaction ' + requestPayload.transactionId + ' response status: Unknown'); + logger.error(this._basicFormatLog() + ' Stopping transaction id ' + this._connectors[transactionConnectorId].transactionId + ' REJECTED with status ' + payload.idTagInfo.status); } } @@ -598,7 +607,8 @@ class ChargingStation { _setConfigurationKeyValue(key, value) { const keyFound = this._getConfigurationKey(key); if (keyFound) { - this._configuration.configurationKey.key = value; + const keyIndex = this._configuration.configurationKey.indexOf(keyFound); + this._configuration.configurationKey[keyIndex].value = value; } } @@ -622,13 +632,13 @@ class ChargingStation { }); } } else { - for (const configuration of commandPayload.key) { - const keyFound = this._getConfigurationKey(configuration); + for (const configurationKey of commandPayload.key) { + const keyFound = this._getConfigurationKey(configurationKey); if (keyFound) { if (Utils.isUndefined(keyFound.visible)) { keyFound.visible = true; } else { - keyFound.visible = Utils.convertToBoolean(configuration.visible); + keyFound.visible = Utils.convertToBoolean(configurationKey.visible); } if (!keyFound.visible) { continue; @@ -639,7 +649,7 @@ class ChargingStation { value: keyFound.value, }); } else { - unknownKey.push(configuration); + unknownKey.push(configurationKey); } } } @@ -658,7 +668,16 @@ class ChargingStation { } else if (keyToChange && !Utils.convertToBoolean(keyToChange.readonly)) { const keyIndex = this._configuration.configurationKey.indexOf(keyToChange); this._configuration.configurationKey[keyIndex].value = commandPayload.value; - if (keyToChange.key === 'HeartBeatInterval' || keyToChange === 'HeartbeatInterval') { + let triggerHeartbeatRestart = false; + if (keyToChange.key === 'HeartBeatInterval') { + this._setConfigurationKeyValue('HeartbeatInterval', commandPayload.value); + triggerHeartbeatRestart = true; + } + if (keyToChange.key === 'HeartbeatInterval') { + this._setConfigurationKeyValue('HeartBeatInterval', commandPayload.value); + triggerHeartbeatRestart = true; + } + if (triggerHeartbeatRestart) { this._heartbeatInterval = Utils.convertToInt(commandPayload.value) * 1000; // Stop heartbeat if (this._heartbeatSetInterval) { @@ -697,10 +716,12 @@ class ChargingStation { async handleRemoteStopTransaction(commandPayload) { for (const connector in this._connectors) { if (this._connectors[connector].transactionId === commandPayload.transactionId) { - this.sendStopTransaction(commandPayload.transactionId, connector); + this.sendStopTransaction(commandPayload.transactionId); + return Constants.OCPP_RESPONSE_ACCEPTED; } } - return Constants.OCPP_RESPONSE_ACCEPTED; + logger.info(this._basicFormatLog() + ' Try to stop remotely a non existing transaction ' + commandPayload.transactionId); + return Constants.OCPP_RESPONSE_REJECTED; } async sendStartTransaction(connectorID, idTag) { @@ -714,7 +735,6 @@ class ChargingStation { return await this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'StartTransaction'); } catch (error) { logger.error(this._basicFormatLog() + ' Send start transaction error: ' + error); - this._resetTransactionOnConnector(connectorID); throw error; } } @@ -722,7 +742,7 @@ class ChargingStation { setTimeout(() => this.sendStartTransaction(connectorID, idTag), timeout); } - async sendStopTransaction(transactionId, connectorID) { + async sendStopTransaction(transactionId) { try { const payload = { transactionId, @@ -730,18 +750,14 @@ class ChargingStation { timestamp: new Date().toISOString(), }; await this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'StopTransaction'); - logger.info(this._basicFormatLog() + ' Transaction ' + this._connectors[connectorID].transactionId + ' STOPPED on ' + this._stationInfo.name + '#' + connectorID); - this.sendStatusNotification(connectorID, 'Available'); } catch (error) { logger.error(this._basicFormatLog() + ' Send stop transaction error: ' + error); throw error; - } finally { - this._resetTransactionOnConnector(connectorID); } } // eslint-disable-next-line class-methods-use-this - async sendMeterValues(connectorID, interval, self) { + async sendMeterValues(connectorID, interval, self, debug = false) { try { const sampledValueLcl = { timestamp: new Date().toISOString(), @@ -753,30 +769,25 @@ class ChargingStation { sampledValueLcl.sampledValue = [meterValuesClone]; } for (let index = 0; index < sampledValueLcl.sampledValue.length; index++) { + const connector = self._connectors[connectorID]; if (sampledValueLcl.sampledValue[index].measurand && sampledValueLcl.sampledValue[index].measurand === 'SoC') { sampledValueLcl.sampledValue[index].value = Utils.getRandomInt(100); - if (sampledValueLcl.sampledValue[index].value > 100) { - logger.info(self._basicFormatLog() + ' MeterValues measurand: ' + - sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register' + - ', value: ' + sampledValueLcl.sampledValue[index].value); + if (sampledValueLcl.sampledValue[index].value > 100 || debug) { + logger.error(`${self._basicFormatLog()} MeterValues measurand ${sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register'}: connectorID ${connectorID}, transaction ${connector.transactionId}, value: ${sampledValueLcl.sampledValue[index].value}`); } } else { // Persist previous value in connector - const connector = self._connectors[connectorID]; - let consumption; - consumption = Utils.getRandomInt(self._stationInfo.maxPower / 3600000 * interval); + const consumption = Utils.getRandomInt(self._stationInfo.maxPower / 3600000 * interval); if (connector && connector.lastConsumptionValue >= 0) { connector.lastConsumptionValue += consumption; } else { connector.lastConsumptionValue = 0; } - consumption = Math.round(connector.lastConsumptionValue * 3600 / interval); - logger.info(self._basicFormatLog() + ' MeterValues: connectorID ' + connectorID + ', transaction ' + connector.transactionId + ', value ' + connector.lastConsumptionValue); + const maxConsumption = self._stationInfo.maxPower * 3600 / interval; + logger.info(`${self._basicFormatLog()} MeterValues measurand ${sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register'}: connectorID ${connectorID}, transaction ${connector.transactionId}, value ${connector.lastConsumptionValue}`); sampledValueLcl.sampledValue[index].value = connector.lastConsumptionValue; - if (sampledValueLcl.sampledValue[index].value > (self._stationInfo.maxPower * 3600 / interval) || sampledValueLcl.sampledValue[index].value < 500) { - logger.info(self._basicFormatLog() + ' MeterValues measurand: ' + - sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'Energy.Active.Import.Register' + - ', value: ' + sampledValueLcl.sampledValue[index].value + '/' + (self._stationInfo.maxPower * 3600 / interval)); + if (sampledValueLcl.sampledValue[index].value > maxConsumption || debug) { + logger.error(`${self._basicFormatLog()} 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}`); } } } @@ -792,18 +803,20 @@ class ChargingStation { } } - async startMeterValues(connectorID, interval, self) { + async startMeterValues(connectorID, interval) { if (!this._connectors[connectorID].transactionStarted) { - logger.debug(`${self._basicFormatLog()} Trying to start MeterValues on connector ID ${connectorID} with no transaction started`); + logger.error(`${this._basicFormatLog()} Trying to start MeterValues on connector ID ${connectorID} with no transaction started`); + return; } else if (this._connectors[connectorID].transactionStarted && !this._connectors[connectorID].transactionId) { - logger.debug(`${self._basicFormatLog()} Trying to start MeterValues on connector ID ${connectorID} with no transaction id`); + logger.error(`${this._basicFormatLog()} Trying to start MeterValues on connector ID ${connectorID} with no transaction id`); + return; } - this._connectors[connectorID].transactionInterval = setInterval(async () => { + this._connectors[connectorID].transactionSetInterval = setInterval(async () => { const sendMeterValues = performance.timerify(this.sendMeterValues); this._performanceObserver.observe({ entryTypes: ['function'], }); - await sendMeterValues(connectorID, interval, self); + await sendMeterValues(connectorID, interval, this); }, interval); } @@ -819,6 +832,17 @@ class ChargingStation { _getConnector(number) { return this._stationInfo.Connectors[number]; } + + _getMaxConnectors() { + let maxConnectors = 0; + if (Array.isArray(this._stationInfo.numberOfConnectors)) { + // Generate some connectors + maxConnectors = this._stationInfo.numberOfConnectors[(this._index - 1) % this._stationInfo.numberOfConnectors.length]; + } else { + maxConnectors = this._stationInfo.numberOfConnectors; + } + return maxConnectors; + } } module.exports = ChargingStation;