X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=1bf867cb304b0361a0e872b61c3b8ee9adfdf717;hb=3574dfd3808a5894699e5fe246f723ebfee93de9;hp=56738bb688287e4138ad0e8736fdb7ada7973e16;hpb=29bf6658d0689f5df26575d3b171163fe1d52d04;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 56738bb6..1bf867cb 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -5,19 +5,20 @@ import { ConfigurationResponse, DefaultRequestResponse, UnlockResponse } from '. import Connectors, { Connector } from '../types/Connectors'; import MeterValue, { MeterValueLocation, MeterValueMeasurand, MeterValuePhase, MeterValueUnit } from '../types/ocpp/1.6/MeterValue'; import { PerformanceObserver, performance } from 'perf_hooks'; +import WebSocket, { MessageEvent } from 'ws'; import AutomaticTransactionGenerator from './AutomaticTransactionGenerator'; import { ChargePointErrorCode } from '../types/ocpp/1.6/ChargePointErrorCode'; import { ChargePointStatus } from '../types/ocpp/1.6/ChargePointStatus'; import ChargingStationInfo from '../types/ChargingStationInfo'; import Configuration from '../utils/Configuration'; -import Constants from '../utils/Constants.js'; +import Constants from '../utils/Constants'; import ElectricUtils from '../utils/ElectricUtils'; import MeasurandValues from '../types/MeasurandValues'; -import OCPPError from './OcppError.js'; +import OCPPError from './OcppError'; +import Requests from '../types/ocpp/1.6/Requests'; import Statistics from '../utils/Statistics'; import Utils from '../utils/Utils'; -import WebSocket from 'ws'; import crypto from 'crypto'; import fs from 'fs'; import logger from '../utils/Logger'; @@ -41,32 +42,31 @@ export default class ChargingStation { private _wsConnection: WebSocket; private _hasStopped: boolean; private _hasSocketRestarted: boolean; + private _connectionTimeout: number; private _autoReconnectRetryCount: number; private _autoReconnectMaxRetries: number; - private _autoReconnectTimeout: number; - private _requests: { [id: string]: [(payload?, requestPayload?) => void, (error?: OCPPError) => void, object] }; - private _messageQueue: any[]; + private _requests: Requests; + private _messageQueue: string[]; private _automaticTransactionGeneration: AutomaticTransactionGenerator; private _authorizedTags: string[]; private _heartbeatInterval: number; private _heartbeatSetInterval: NodeJS.Timeout; + private _webSocketPingSetInterval: NodeJS.Timeout; private _statistics: Statistics; private _performanceObserver: PerformanceObserver; constructor(index: number, stationTemplateFile: string) { this._index = index; this._stationTemplateFile = stationTemplateFile; - this._connectors = {}; + this._connectors = {} as Connectors; this._initialize(); this._hasStopped = false; this._hasSocketRestarted = false; this._autoReconnectRetryCount = 0; - this._autoReconnectMaxRetries = Configuration.getAutoReconnectMaxRetries(); // -1 for unlimited - this._autoReconnectTimeout = Configuration.getAutoReconnectTimeout() * 1000; // Ms, zero for disabling - this._requests = {}; - this._messageQueue = []; + this._requests = {} as Requests; + this._messageQueue = [] as string[]; this._authorizedTags = this._loadAndGetAuthorizedTags(); } @@ -110,9 +110,11 @@ export default class ChargingStation { ...!Utils.isUndefined(this._stationInfo.chargeBoxSerialNumberPrefix) && { chargeBoxSerialNumber: this._stationInfo.chargeBoxSerialNumberPrefix }, ...!Utils.isUndefined(this._stationInfo.firmwareVersion) && { firmwareVersion: this._stationInfo.firmwareVersion }, }; - this._configuration = this._getConfiguration(); + this._configuration = this._getTemplateChargingStationConfiguration(); this._supervisionUrl = this._getSupervisionURL(); this._wsConnectionUrl = this._supervisionUrl + '/' + this._stationInfo.name; + this._connectionTimeout = this._getConnectionTimeout() * 1000; // Ms, zero for disabling + this._autoReconnectMaxRetries = this._getAutoReconnectMaxRetries(); // -1 for unlimited // Build connectors if needed const maxConnectors = this._getMaxNumberOfConnectors(); if (maxConnectors <= 0) { @@ -183,7 +185,7 @@ export default class ChargingStation { return Utils.logPrefix(` ${this._stationInfo.name}:`); } - _getConfiguration(): ChargingStationConfiguration { + _getTemplateChargingStationConfiguration(): ChargingStationConfiguration { return this._stationInfo.Configuration ? this._stationInfo.Configuration : {} as ChargingStationConfiguration; } @@ -242,6 +244,26 @@ export default class ChargingStation { return trxCount; } + _getConnectionTimeout(): number { + if (!Utils.isUndefined(this._stationInfo.connectionTimeout)) { + return this._stationInfo.connectionTimeout; + } + if (!Utils.isUndefined(Configuration.getConnectionTimeout())) { + return Configuration.getConnectionTimeout(); + } + return 30; + } + + _getAutoReconnectMaxRetries(): number { + if (!Utils.isUndefined(this._stationInfo.autoReconnectMaxRetries)) { + return this._stationInfo.autoReconnectMaxRetries; + } + if (!Utils.isUndefined(Configuration.getAutoReconnectMaxRetries())) { + return Configuration.getAutoReconnectMaxRetries(); + } + return -1; + } + _getPowerDivider(): number { let powerDivider = this._getNumberOfConnectors(); if (this._stationInfo.powerSharedByConnectors) { @@ -293,7 +315,7 @@ export default class ChargingStation { return !Utils.isUndefined(this._stationInfo.voltageOut) ? Utils.convertToInt(this._stationInfo.voltageOut) : defaultVoltageOut; } - _getTransactionidTag(transactionId: number): string { + _getTransactionIdTag(transactionId: number): string { for (const connector in this._connectors) { if (this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) { return this.getConnector(Utils.convertToInt(connector)).idTag; @@ -309,7 +331,7 @@ export default class ChargingStation { const supervisionUrls = Utils.cloneObject(this._stationInfo.supervisionURL ? this._stationInfo.supervisionURL : Configuration.getSupervisionURLs()) as string | string[]; let indexUrl = 0; if (!Utils.isEmptyArray(supervisionUrls)) { - if (Configuration.getDistributeStationToTenantEqually()) { + if (Configuration.getDistributeStationsToTenantsEqually()) { indexUrl = this._index % supervisionUrls.length; } else { // Get a random url @@ -320,6 +342,10 @@ export default class ChargingStation { return supervisionUrls as string; } + _getReconnectExponentialDelay(): boolean { + return !Utils.isUndefined(this._stationInfo.reconnectExponentialDelay) ? this._stationInfo.reconnectExponentialDelay : false; + } + _getAuthorizeRemoteTxRequests(): boolean { const authorizeRemoteTxRequests = this._getConfigurationKey('AuthorizeRemoteTxRequests'); return authorizeRemoteTxRequests ? Utils.convertToBoolean(authorizeRemoteTxRequests.value) : false; @@ -330,21 +356,25 @@ export default class ChargingStation { return localAuthListEnabled ? Utils.convertToBoolean(localAuthListEnabled.value) : false; } - _startMessageSequence(): void { + async _startMessageSequence(): Promise { + // Start WebSocket ping + this._startWebSocketPing(); // Start heartbeat this._startHeartbeat(); // Initialize connectors status for (const connector in this._connectors) { - if (!this.getConnector(Utils.convertToInt(connector)).transactionStarted) { - if (!this.getConnector(Utils.convertToInt(connector)).status && this.getConnector(Utils.convertToInt(connector)).bootStatus) { - this.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).bootStatus); - } else if (!this._hasStopped && this.getConnector(Utils.convertToInt(connector)).status) { - this.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).status); - } else { - this.sendStatusNotification(Utils.convertToInt(connector), ChargePointStatus.AVAILABLE); - } + if (!this._hasStopped && !this.getConnector(Utils.convertToInt(connector)).status && this.getConnector(Utils.convertToInt(connector)).bootStatus) { + // Send status in template at startup + await this.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).bootStatus); + } else if (this._hasStopped && this.getConnector(Utils.convertToInt(connector)).bootStatus) { + // Send status in template after reset + await this.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).bootStatus); + } else if (!this._hasStopped && this.getConnector(Utils.convertToInt(connector)).status) { + // Send previous status at template reload + await this.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).status); } else { - this.sendStatusNotification(Utils.convertToInt(connector), ChargePointStatus.CHARGING); + // Send default status + await this.sendStatusNotification(Utils.convertToInt(connector), ChargePointStatus.AVAILABLE); } } // Start the ATG @@ -362,6 +392,8 @@ export default class ChargingStation { } async _stopMessageSequence(reason: StopTransactionReason = StopTransactionReason.NONE): Promise { + // Stop WebSocket ping + this._stopWebSocketPing(); // Stop heartbeat this._stopHeartbeat(); // Stop the ATG @@ -378,14 +410,46 @@ export default class ChargingStation { } } + _startWebSocketPing(): void { + const webSocketPingInterval: number = this._getConfigurationKey('WebSocketPingInterval') ? Utils.convertToInt(this._getConfigurationKey('WebSocketPingInterval').value) : 0; + if (webSocketPingInterval > 0 && !this._webSocketPingSetInterval) { + this._webSocketPingSetInterval = setInterval(() => { + if (this._wsConnection?.readyState === WebSocket.OPEN) { + this._wsConnection.ping((): void => { }); + } + }, webSocketPingInterval * 1000); + logger.info(this._logPrefix() + ' WebSocket ping started every ' + Utils.secondsToHHMMSS(webSocketPingInterval)); + } else if (this._webSocketPingSetInterval) { + logger.info(this._logPrefix() + ' WebSocket ping every ' + Utils.secondsToHHMMSS(webSocketPingInterval) + ' already started'); + } else { + logger.error(`${this._logPrefix()} WebSocket ping interval set to ${webSocketPingInterval ? Utils.secondsToHHMMSS(webSocketPingInterval) : webSocketPingInterval}, not starting the WebSocket ping`); + } + } + + _stopWebSocketPing(): void { + if (this._webSocketPingSetInterval) { + clearInterval(this._webSocketPingSetInterval); + this._webSocketPingSetInterval = null; + } + } + + _restartWebSocketPing(): void { + // Stop WebSocket ping + this._stopWebSocketPing(); + // Start WebSocket ping + this._startWebSocketPing(); + } + _startHeartbeat(): void { if (this._heartbeatInterval && this._heartbeatInterval > 0 && !this._heartbeatSetInterval) { - this._heartbeatSetInterval = setInterval(() => { - this.sendHeartbeat(); + this._heartbeatSetInterval = setInterval(async () => { + await this.sendHeartbeat(); }, this._heartbeatInterval); logger.info(this._logPrefix() + ' Heartbeat started every ' + Utils.milliSecondsToHHMMSS(this._heartbeatInterval)); + } else if (this._heartbeatSetInterval) { + logger.info(this._logPrefix() + ' Heartbeat every ' + Utils.milliSecondsToHHMMSS(this._heartbeatInterval) + ' already started'); } else { - logger.error(`${this._logPrefix()} Heartbeat interval set to ${Utils.milliSecondsToHHMMSS(this._heartbeatInterval)}, not starting the heartbeat`); + logger.error(`${this._logPrefix()} Heartbeat interval set to ${this._heartbeatInterval ? Utils.milliSecondsToHHMMSS(this._heartbeatInterval) : this._heartbeatInterval}, not starting the heartbeat`); } } @@ -396,6 +460,13 @@ export default class ChargingStation { } } + _restartHeartbeat(): void { + // Stop heartbeat + this._stopHeartbeat(); + // Start heartbeat + this._startHeartbeat(); + } + _startAuthorizationFileMonitoring(): void { // eslint-disable-next-line @typescript-eslint/no-unused-vars fs.watchFile(this._getAuthorizationFile(), (current, previous) => { @@ -420,6 +491,7 @@ export default class ChargingStation { this._automaticTransactionGeneration) { this._automaticTransactionGeneration.stop().catch(() => { }); } + // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed } catch (error) { logger.error(this._logPrefix() + ' Charging station template file monitoring error: %j', error); } @@ -451,12 +523,19 @@ export default class ChargingStation { } } - start(): void { - if (!this._wsConnectionUrl) { - this._wsConnectionUrl = this._supervisionUrl + '/' + this._stationInfo.name; + _openWSConnection(options?: WebSocket.ClientOptions): void { + if (Utils.isUndefined(options)) { + options = {} as WebSocket.ClientOptions; } - this._wsConnection = new WebSocket(this._wsConnectionUrl, 'ocpp' + Constants.OCPP_VERSION_16); + if (Utils.isUndefined(options.handshakeTimeout)) { + options.handshakeTimeout = this._connectionTimeout; + } + this._wsConnection = new WebSocket(this._wsConnectionUrl, 'ocpp' + Constants.OCPP_VERSION_16, options); logger.info(this._logPrefix() + ' Will communicate through URL ' + this._supervisionUrl); + } + + start(): void { + this._openWSConnection(); // Monitor authorization file this._startAuthorizationFileMonitoring(); // Monitor station template file @@ -471,23 +550,27 @@ export default class ChargingStation { this._wsConnection.on('open', this.onOpen.bind(this)); // Handle Socket ping this._wsConnection.on('ping', this.onPing.bind(this)); + // Handle Socket pong + this._wsConnection.on('pong', this.onPong.bind(this)); } async stop(reason: StopTransactionReason = StopTransactionReason.NONE): Promise { - // Stop + // Stop message sequence await this._stopMessageSequence(reason); // eslint-disable-next-line guard-for-in for (const connector in this._connectors) { await this.sendStatusNotification(Utils.convertToInt(connector), ChargePointStatus.UNAVAILABLE); } - if (this._wsConnection && this._wsConnection.readyState === WebSocket.OPEN) { + if (this._wsConnection?.readyState === WebSocket.OPEN) { this._wsConnection.close(); } this._hasStopped = true; } - _reconnect(error): void { - logger.error(this._logPrefix() + ' Socket: abnormally closed %j', error); + async _reconnect(error): Promise { + logger.error(this._logPrefix() + ' Socket: abnormally closed: %j', error); + // Stop heartbeat + this._stopHeartbeat(); // Stop the ATG if needed if (this._stationInfo.AutomaticTransactionGenerator.enable && this._stationInfo.AutomaticTransactionGenerator.stopOnConnectionFailure && @@ -495,32 +578,30 @@ export default class ChargingStation { !this._automaticTransactionGeneration.timeToStop) { this._automaticTransactionGeneration.stop().catch(() => { }); } - // Stop heartbeat - this._stopHeartbeat(); - if (this._autoReconnectTimeout !== 0 && - (this._autoReconnectRetryCount < this._autoReconnectMaxRetries || this._autoReconnectMaxRetries === -1)) { - logger.error(`${this._logPrefix()} Socket: connection retry with timeout ${this._autoReconnectTimeout}ms`); + if (this._autoReconnectRetryCount < this._autoReconnectMaxRetries || this._autoReconnectMaxRetries === -1) { this._autoReconnectRetryCount++; - setTimeout(() => { - logger.error(this._logPrefix() + ' Socket: reconnecting try #' + this._autoReconnectRetryCount.toString()); - this.start(); - }, this._autoReconnectTimeout); - } else if (this._autoReconnectTimeout !== 0 || this._autoReconnectMaxRetries !== -1) { - logger.error(`${this._logPrefix()} Socket: max retries reached (${this._autoReconnectRetryCount}) or retry disabled (${this._autoReconnectTimeout})`); + const reconnectDelay = (this._getReconnectExponentialDelay() ? Utils.exponentialDelay(this._autoReconnectRetryCount) : this._connectionTimeout); + logger.error(`${this._logPrefix()} Socket: connection retry in ${Utils.roundTo(reconnectDelay, 2)}ms, timeout ${reconnectDelay - 100}ms`); + await Utils.sleep(reconnectDelay); + logger.error(this._logPrefix() + ' Socket: reconnecting try #' + this._autoReconnectRetryCount.toString()); + this._openWSConnection({ handshakeTimeout: reconnectDelay - 100 }); + } else if (this._autoReconnectMaxRetries !== -1) { + logger.error(`${this._logPrefix()} Socket: max retries reached (${this._autoReconnectRetryCount}) or retry disabled (${this._autoReconnectMaxRetries})`); } } - onOpen(): void { + async onOpen(): Promise { logger.info(`${this._logPrefix()} Is connected to server through ${this._wsConnectionUrl}`); - if (!this._hasSocketRestarted) { + if (!this._hasSocketRestarted || this._hasStopped) { // Send BootNotification - this.sendBootNotification(); + await this.sendBootNotification(); } + await this._startMessageSequence(); if (this._hasSocketRestarted) { - this._startMessageSequence(); if (!Utils.isEmptyArray(this._messageQueue)) { - this._messageQueue.forEach((message) => { - if (this._wsConnection && this._wsConnection.readyState === WebSocket.OPEN) { + this._messageQueue.forEach((message, index) => { + if (this._wsConnection?.readyState === WebSocket.OPEN) { + this._messageQueue.splice(index, 1); this._wsConnection.send(message); } }); @@ -530,28 +611,28 @@ export default class ChargingStation { this._hasSocketRestarted = false; } - onError(error): void { - switch (error) { + async onError(errorEvent): Promise { + switch (errorEvent.code) { case 'ECONNREFUSED': this._hasSocketRestarted = true; - this._reconnect(error); + await this._reconnect(errorEvent); break; default: - logger.error(this._logPrefix() + ' Socket error: %j', error); + logger.error(this._logPrefix() + ' Socket error: %j', errorEvent); break; } } - onClose(error): void { - switch (error) { + async onClose(closeEvent): Promise { + switch (closeEvent) { case 1000: // Normal close case 1005: - logger.info(this._logPrefix() + ' Socket normally closed %j', error); + logger.info(this._logPrefix() + ' Socket normally closed: %j', closeEvent); this._autoReconnectRetryCount = 0; break; default: // Abnormal close this._hasSocketRestarted = true; - this._reconnect(error); + await this._reconnect(closeEvent); break; } } @@ -560,11 +641,15 @@ export default class ChargingStation { logger.debug(this._logPrefix() + ' Has received a WS ping (rfc6455) from the server'); } - async onMessage(message): Promise { + onPong(): void { + logger.debug(this._logPrefix() + ' Has received a WS pong (rfc6455) from the server'); + } + + async onMessage(messageEvent: MessageEvent): Promise { let [messageType, messageId, commandName, commandPayload, errorDetails] = [0, '', Constants.ENTITY_CHARGING_STATION, '', '']; try { // Parse the message - [messageType, messageId, commandName, commandPayload, errorDetails] = JSON.parse(message); + [messageType, messageId, commandName, commandPayload, errorDetails] = JSON.parse(messageEvent.toString()); // Check the Type of message switch (messageType) { @@ -618,9 +703,9 @@ export default class ChargingStation { } } catch (error) { // Log - logger.error('%s Incoming message %j processing error %s on request content type %s', this._logPrefix(), message, error, this._requests[messageId]); + logger.error('%s Incoming message %j processing error %s on request content type %s', this._logPrefix(), messageEvent, error, this._requests[messageId]); // Send error - await this.sendError(messageId, error, commandName); + messageType !== Constants.OCPP_JSON_CALL_ERROR_MESSAGE && await this.sendError(messageId, error, commandName); } } @@ -676,7 +761,7 @@ export default class ChargingStation { } async sendStopTransaction(transactionId: number, reason: StopTransactionReason = StopTransactionReason.NONE): Promise { - const idTag = this._getTransactionidTag(transactionId); + const idTag = this._getTransactionIdTag(transactionId); try { const payload = { transactionId, @@ -951,7 +1036,7 @@ export default class ChargingStation { break; } // Check if wsConnection is ready - if (this._wsConnection && this._wsConnection.readyState === WebSocket.OPEN) { + if (this._wsConnection?.readyState === WebSocket.OPEN) { if (this.getEnableStatistics()) { this._statistics.addMessage(commandName, messageType); } @@ -972,7 +1057,7 @@ export default class ChargingStation { this._messageQueue.push(messageToSend); } // Reject it - return rejectCallback(new OCPPError(commandParams.code ? commandParams.code : Constants.OCPP_ERROR_GENERIC_ERROR, commandParams.message ? commandParams.message : `Web socket closed for message id '${messageId}' with content '${messageToSend}', message buffered`, commandParams.details ? commandParams.details : {})); + return rejectCallback(new OCPPError(commandParams.code ? commandParams.code : Constants.OCPP_ERROR_GENERIC_ERROR, commandParams.message ? commandParams.message : `WebSocket closed for message id '${messageId}' with content '${messageToSend}', message buffered`, commandParams.details ? commandParams.details : {})); } // Response? if (messageType === Constants.OCPP_JSON_CALL_RESULT_MESSAGE) { @@ -998,7 +1083,7 @@ export default class ChargingStation { if (self.getEnableStatistics()) { self._statistics.addMessage(commandName, messageType); } - logger.debug(`${self._logPrefix()} Error %j occurred when calling command %s with parameters %j`, error, commandName, commandParams); + logger.debug(`${self._logPrefix()} Error: %j occurred when calling command %s with parameters: %j`, error, commandName, commandParams); // Build Exception // eslint-disable-next-line no-empty-function self._requests[messageId] = [() => { }, () => { }, {}]; // Properly format the request @@ -1019,10 +1104,10 @@ export default class ChargingStation { handleResponseBootNotification(payload, requestPayload): void { if (payload.status === 'Accepted') { - this._heartbeatInterval = payload.interval * 1000; + this._heartbeatInterval = Utils.convertToInt(payload.interval) * 1000; + this._heartbeatSetInterval ? this._restartHeartbeat() : this._startHeartbeat(); this._addConfigurationKey('HeartBeatInterval', payload.interval); this._addConfigurationKey('HeartbeatInterval', payload.interval, false, false); - this._startMessageSequence(); this._hasStopped && (this._hasStopped = false); } else if (payload.status === 'Pending') { logger.info(this._logPrefix() + ' Charging station in pending state on the central server'); @@ -1048,7 +1133,7 @@ export default class ChargingStation { handleResponseStartTransaction(payload: StartTransactionResponse, requestPayload): void { const connectorId = Utils.convertToInt(requestPayload.connectorId); if (this.getConnector(connectorId).transactionStarted) { - logger.debug(this._logPrefix() + ' Try to start a transaction on an already used connector ' + connectorId.toString() + ': %j', this.getConnector(connectorId)); + logger.debug(this._logPrefix() + ' Trying to start a transaction on an already used connector ' + connectorId.toString() + ': %j', this.getConnector(connectorId)); return; } @@ -1060,7 +1145,7 @@ export default class ChargingStation { } } if (!transactionConnectorId) { - logger.error(this._logPrefix() + ' Try to start a transaction on a non existing connector Id ' + connectorId.toString()); + logger.error(this._logPrefix() + ' Trying to start a transaction on a non existing connector Id ' + connectorId.toString()); return; } if (payload.idTagInfo?.status === AuthorizationStatus.ACCEPTED) { @@ -1092,7 +1177,7 @@ export default class ChargingStation { } } if (!transactionConnectorId) { - logger.error(this._logPrefix() + ' Try to stop a non existing transaction ' + requestPayload.transactionId); + logger.error(this._logPrefix() + ' Trying to stop a non existing transaction ' + requestPayload.transactionId); return; } if (payload.idTagInfo?.status === AuthorizationStatus.ACCEPTED) { @@ -1156,7 +1241,7 @@ export default class ChargingStation { async handleRequestUnlockConnector(commandPayload): Promise { const connectorId = Utils.convertToInt(commandPayload.connectorId); if (connectorId === 0) { - logger.error(this._logPrefix() + ' Try to unlock connector ' + connectorId.toString()); + logger.error(this._logPrefix() + ' Trying to unlock connector ' + connectorId.toString()); return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED; } if (this.getConnector(connectorId).transactionStarted) { @@ -1246,22 +1331,26 @@ export default class ChargingStation { return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED; } else if (keyToChange && !keyToChange.readonly) { const keyIndex = this._configuration.configurationKey.indexOf(keyToChange); - this._configuration.configurationKey[keyIndex].value = commandPayload.value; + let valueChanged = false; + if (this._configuration.configurationKey[keyIndex].value !== commandPayload.value) { + this._configuration.configurationKey[keyIndex].value = commandPayload.value as string; + valueChanged = true; + } let triggerHeartbeatRestart = false; - if (keyToChange.key === 'HeartBeatInterval') { + if (keyToChange.key === 'HeartBeatInterval' && valueChanged) { this._setConfigurationKeyValue('HeartbeatInterval', commandPayload.value); triggerHeartbeatRestart = true; } - if (keyToChange.key === 'HeartbeatInterval') { + if (keyToChange.key === 'HeartbeatInterval' && valueChanged) { this._setConfigurationKeyValue('HeartBeatInterval', commandPayload.value); triggerHeartbeatRestart = true; } if (triggerHeartbeatRestart) { this._heartbeatInterval = Utils.convertToInt(commandPayload.value) * 1000; - // Stop heartbeat - this._stopHeartbeat(); - // Start heartbeat - this._startHeartbeat(); + this._restartHeartbeat(); + } + if (keyToChange.key === 'WebSocketPingInterval' && valueChanged) { + this._restartWebSocketPing(); } if (keyToChange.reboot) { return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED; @@ -1297,7 +1386,7 @@ export default class ChargingStation { return Constants.OCPP_RESPONSE_ACCEPTED; } } - logger.info(this._logPrefix() + ' Try to stop remotely a non existing transaction ' + transactionId.toString()); + logger.info(this._logPrefix() + ' Trying to remote stop a non existing transaction ' + transactionId.toString()); return Constants.OCPP_RESPONSE_REJECTED; } }