X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=610b2b55c0012feafafa17a4bc5a7591f5ac3118;hb=df6dddca99d23c0458eb4758f651a3be8e5d2d91;hp=4adaa289486b79113eaa1ee72dbe48cbb29d860a;hpb=1f761b9af1c816fa55ce0d35b69666b7a5c28579;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 4adaa289..610b2b55 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -1,4 +1,5 @@ import { AuthorizationStatus, StartTransactionRequest, StartTransactionResponse, StopTransactionReason, StopTransactionRequest, StopTransactionResponse } from '../types/ocpp/1.6/Transaction'; +import { BootNotificationRequest, ChangeConfigurationRequest, GetConfigurationRequest, HeartbeatRequest, IncomingRequestCommand, RemoteStartTransactionRequest, RemoteStopTransactionRequest, RequestCommand, ResetRequest, SetChargingProfileRequest, StatusNotificationRequest, UnlockConnectorRequest } from '../types/ocpp/1.6/Requests'; import { BootNotificationResponse, ChangeConfigurationResponse, DefaultResponse, GetConfigurationResponse, HeartbeatResponse, RegistrationStatus, SetChargingProfileResponse, StatusNotificationResponse, UnlockConnectorResponse } from '../types/ocpp/1.6/RequestResponses'; import { ChargingProfile, ChargingProfilePurposeType } from '../types/ocpp/1.6/ChargingProfile'; import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration'; @@ -6,7 +7,7 @@ import ChargingStationTemplate, { PowerOutType } from '../types/ChargingStationT import Connectors, { Connector } from '../types/Connectors'; import { MeterValue, MeterValueLocation, MeterValueMeasurand, MeterValuePhase, MeterValueUnit, MeterValuesRequest, MeterValuesResponse, SampledValue } from '../types/ocpp/1.6/MeterValues'; import { PerformanceObserver, performance } from 'perf_hooks'; -import Requests, { BootNotificationRequest, ChangeConfigurationRequest, GetConfigurationRequest, HeartbeatRequest, RemoteStartTransactionRequest, RemoteStopTransactionRequest, ResetRequest, SetChargingProfileRequest, StatusNotificationRequest, UnlockConnectorRequest } from '../types/ocpp/1.6/Requests'; +import Requests, { IncomingRequest, Request } from '../types/ocpp/Requests'; import WebSocket, { MessageEvent } from 'ws'; import AutomaticTransactionGenerator from './AutomaticTransactionGenerator'; @@ -16,8 +17,12 @@ import ChargingStationInfo from '../types/ChargingStationInfo'; import Configuration from '../utils/Configuration'; import Constants from '../utils/Constants'; import ElectricUtils from '../utils/ElectricUtils'; +import { ErrorType } from '../types/ocpp/ErrorType'; import MeasurandValues from '../types/MeasurandValues'; +import { MessageType } from '../types/ocpp/MessageType'; +import { OCPPConfigurationKey } from '../types/ocpp/Configuration'; import OCPPError from './OcppError'; +import { StandardParametersKey } from '../types/ocpp/1.6/Configuration'; import Statistics from '../utils/Statistics'; import Utils from '../utils/Utils'; import { WebSocketCloseEventStatusCode } from '../types/WebSocket'; @@ -44,7 +49,6 @@ export default class ChargingStation { private _messageQueue: string[]; private _automaticTransactionGeneration: AutomaticTransactionGenerator; private _authorizedTags: string[]; - private _heartbeatInterval: number; private _heartbeatSetInterval: NodeJS.Timeout; private _webSocketPingSetInterval: NodeJS.Timeout; private _statistics: Statistics; @@ -153,9 +157,9 @@ export default class ChargingStation { } } // OCPP parameters - this._addConfigurationKey('NumberOfConnectors', this._getNumberOfConnectors().toString(), true); - if (!this._getConfigurationKey('MeterValuesSampledData')) { - this._addConfigurationKey('MeterValuesSampledData', MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER); + this._addConfigurationKey(StandardParametersKey.NumberOfConnectors, this._getNumberOfConnectors().toString(), true); + if (!this._getConfigurationKey(StandardParametersKey.MeterValuesSampledData)) { + this._addConfigurationKey(StandardParametersKey.MeterValuesSampledData, MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER); } this._stationInfo.powerDivider = this._getPowerDivider(); if (this.getEnableStatistics()) { @@ -274,6 +278,7 @@ export default class ChargingStation { return -1; } + // 0 for disabling _getRegistrationMaxRetries(): number { if (!Utils.isUndefined(this._stationInfo.registrationMaxRetries)) { return this._stationInfo.registrationMaxRetries; @@ -371,13 +376,25 @@ export default class ChargingStation { return !Utils.isUndefined(this._stationInfo.reconnectExponentialDelay) ? this._stationInfo.reconnectExponentialDelay : false; } + _getHeartbeatInterval(): number { + const HeartbeatInterval = this._getConfigurationKey(StandardParametersKey.HeartbeatInterval); + if (HeartbeatInterval) { + return Utils.convertToInt(HeartbeatInterval.value) * 1000; + } + const HeartBeatInterval = this._getConfigurationKey(StandardParametersKey.HeartBeatInterval); + if (HeartBeatInterval) { + return Utils.convertToInt(HeartBeatInterval.value) * 1000; + } + return 0; + } + _getAuthorizeRemoteTxRequests(): boolean { - const authorizeRemoteTxRequests = this._getConfigurationKey('AuthorizeRemoteTxRequests'); + const authorizeRemoteTxRequests = this._getConfigurationKey(StandardParametersKey.AuthorizeRemoteTxRequests); return authorizeRemoteTxRequests ? Utils.convertToBoolean(authorizeRemoteTxRequests.value) : false; } _getLocalAuthListEnabled(): boolean { - const localAuthListEnabled = this._getConfigurationKey('LocalAuthListEnabled'); + const localAuthListEnabled = this._getConfigurationKey(StandardParametersKey.LocalAuthListEnabled); return localAuthListEnabled ? Utils.convertToBoolean(localAuthListEnabled.value) : false; } @@ -438,7 +455,7 @@ export default class ChargingStation { } _startWebSocketPing(): void { - const webSocketPingInterval: number = this._getConfigurationKey('WebSocketPingInterval') ? Utils.convertToInt(this._getConfigurationKey('WebSocketPingInterval').value) : 0; + const webSocketPingInterval: number = this._getConfigurationKey(StandardParametersKey.WebSocketPingInterval) ? Utils.convertToInt(this._getConfigurationKey(StandardParametersKey.WebSocketPingInterval).value) : 0; if (webSocketPingInterval > 0 && !this._webSocketPingSetInterval) { this._webSocketPingSetInterval = setInterval(() => { if (this._isWebSocketOpen()) { @@ -468,15 +485,15 @@ export default class ChargingStation { } _startHeartbeat(): void { - if (this._heartbeatInterval && this._heartbeatInterval > 0 && !this._heartbeatSetInterval) { + if (this._getHeartbeatInterval() && this._getHeartbeatInterval() > 0 && !this._heartbeatSetInterval) { this._heartbeatSetInterval = setInterval(async () => { await this.sendHeartbeat(); - }, this._heartbeatInterval); - logger.info(this._logPrefix() + ' Heartbeat started every ' + Utils.milliSecondsToHHMMSS(this._heartbeatInterval)); + }, this._getHeartbeatInterval()); + logger.info(this._logPrefix() + ' Heartbeat started every ' + Utils.milliSecondsToHHMMSS(this._getHeartbeatInterval())); } else if (this._heartbeatSetInterval) { - logger.info(this._logPrefix() + ' Heartbeat every ' + Utils.milliSecondsToHHMMSS(this._heartbeatInterval) + ' already started'); + logger.info(this._logPrefix() + ' Heartbeat every ' + Utils.milliSecondsToHHMMSS(this._getHeartbeatInterval()) + ' already started'); } else { - logger.error(`${this._logPrefix()} Heartbeat interval set to ${this._heartbeatInterval ? Utils.milliSecondsToHHMMSS(this._heartbeatInterval) : this._heartbeatInterval}, not starting the heartbeat`); + logger.error(`${this._logPrefix()} Heartbeat interval set to ${this._getHeartbeatInterval() ? Utils.milliSecondsToHHMMSS(this._getHeartbeatInterval()) : this._getHeartbeatInterval()}, not starting the heartbeat`); } } @@ -618,7 +635,7 @@ export default class ChargingStation { this._openWSConnection({ handshakeTimeout: reconnectDelay - 100 }); this._hasSocketRestarted = true; } else if (this._getAutoReconnectMaxRetries() !== -1) { - logger.error(`${this._logPrefix()} Socket: max retries reached (${this._autoReconnectRetryCount}) or retry disabled (${this._getAutoReconnectMaxRetries()})`); + logger.error(`${this._logPrefix()} Socket reconnect failure: max retries reached (${this._autoReconnectRetryCount}) or retry disabled (${this._getAutoReconnectMaxRetries()})`); } } @@ -646,7 +663,7 @@ export default class ChargingStation { } } } else { - logger.error(`${this._logPrefix()} Registration: max retries reached (${this._getRegistrationMaxRetries()}) or retry disabled (${this._getRegistrationMaxRetries()})`); + logger.error(`${this._logPrefix()} Registration failure: max retries reached (${this._getRegistrationMaxRetries()}) or retry disabled (${this._getRegistrationMaxRetries()})`); } this._autoReconnectRetryCount = 0; this._hasSocketRestarted = false; @@ -684,15 +701,19 @@ export default class ChargingStation { } async onMessage(messageEvent: MessageEvent): Promise { - let [messageType, messageId, commandName, commandPayload, errorDetails] = [0, '', Constants.ENTITY_CHARGING_STATION, '', '']; + let [messageType, messageId, commandName, commandPayload, errorDetails]: IncomingRequest = [0, '', '' as IncomingRequestCommand, '', '']; + let responseCallback: (payload?, requestPayload?) => void; + let rejectCallback: (error: OCPPError) => void; + let requestPayload: Record; + let errMsg: string; try { // Parse the message - [messageType, messageId, commandName, commandPayload, errorDetails] = JSON.parse(messageEvent.toString()); + [messageType, messageId, commandName, commandPayload, errorDetails] = JSON.parse(messageEvent.toString()) as IncomingRequest; // Check the Type of message switch (messageType) { // Incoming Message - case Constants.OCPP_JSON_CALL_MESSAGE: + case MessageType.CALL_MESSAGE: if (this.getEnableStatistics()) { this._statistics.addMessage(commandName, messageType); } @@ -700,10 +721,8 @@ export default class ChargingStation { await this.handleRequest(messageId, commandName, commandPayload); break; // Outcome Message - case Constants.OCPP_JSON_CALL_RESULT_MESSAGE: + case MessageType.CALL_RESULT_MESSAGE: // Respond - // eslint-disable-next-line no-case-declarations - let responseCallback; let requestPayload; if (Utils.isIterable(this._requests[messageId])) { [responseCallback, , requestPayload] = this._requests[messageId]; } else { @@ -717,13 +736,11 @@ export default class ChargingStation { responseCallback(commandName, requestPayload); break; // Error Message - case Constants.OCPP_JSON_CALL_ERROR_MESSAGE: + case MessageType.CALL_ERROR_MESSAGE: if (!this._requests[messageId]) { // Error throw new Error(`Error request for unknown message id ${messageId}`); } - // eslint-disable-next-line no-case-declarations - let rejectCallback; if (Utils.isIterable(this._requests[messageId])) { [, rejectCallback] = this._requests[messageId]; } else { @@ -734,35 +751,32 @@ export default class ChargingStation { break; // Error default: - // eslint-disable-next-line no-case-declarations - const errMsg = `${this._logPrefix()} Wrong message type ${messageType}`; + errMsg = `${this._logPrefix()} Wrong message type ${messageType}`; logger.error(errMsg); throw new Error(errMsg); } } catch (error) { // Log - logger.error('%s Incoming message %j processing error %s on request content type %s', this._logPrefix(), messageEvent, error, this._requests[messageId]); + logger.error('%s Incoming message %j processing error %j on request content type %j', this._logPrefix(), messageEvent, error, this._requests[messageId]); // Send error - messageType !== Constants.OCPP_JSON_CALL_ERROR_MESSAGE && await this.sendError(messageId, error, commandName); + messageType !== MessageType.CALL_ERROR_MESSAGE && await this.sendError(messageId, error, commandName); } } async sendHeartbeat(): Promise { try { const payload: HeartbeatRequest = {}; - await this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'Heartbeat'); + await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, RequestCommand.HEARTBEAT); } catch (error) { - logger.error(this._logPrefix() + ' Send Heartbeat error: %j', error); - throw error; + this.handleRequestError(RequestCommand.HEARTBEAT, error); } } async sendBootNotification(): Promise { try { - return await this.sendMessage(Utils.generateUUID(), this._bootNotificationRequest, Constants.OCPP_JSON_CALL_MESSAGE, 'BootNotification') as BootNotificationResponse; + return await this.sendMessage(Utils.generateUUID(), this._bootNotificationRequest, MessageType.CALL_MESSAGE, RequestCommand.BOOT_NOTIFICATION) as BootNotificationResponse; } catch (error) { - logger.error(this._logPrefix() + ' Send BootNotification error: %j', error); - throw error; + this.handleRequestError(RequestCommand.BOOT_NOTIFICATION, error); } } @@ -774,10 +788,9 @@ export default class ChargingStation { errorCode, status, }; - await this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'StatusNotification'); + await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, RequestCommand.STATUS_NOTIFICATION); } catch (error) { - logger.error(this._logPrefix() + ' Send StatusNotification error: %j', error); - throw error; + this.handleRequestError(RequestCommand.STATUS_NOTIFICATION, error); } } @@ -789,10 +802,9 @@ export default class ChargingStation { meterStart: 0, timestamp: new Date().toISOString(), }; - return await this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'StartTransaction') as StartTransactionResponse; + return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, RequestCommand.START_TRANSACTION) as StartTransactionResponse; } catch (error) { - logger.error(this._logPrefix() + ' Send StartTransaction error: %j', error); - throw error; + this.handleRequestError(RequestCommand.START_TRANSACTION, error); } } @@ -806,10 +818,9 @@ export default class ChargingStation { timestamp: new Date().toISOString(), ...reason && { reason }, }; - return await this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'StopTransaction') as StartTransactionResponse; + return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, RequestCommand.STOP_TRANSACTION) as StartTransactionResponse; } catch (error) { - logger.error(this._logPrefix() + ' Send StopTransaction error: %j', error); - throw error; + this.handleRequestError(RequestCommand.STOP_TRANSACTION, error); } } @@ -824,7 +835,7 @@ export default class ChargingStation { for (let index = 0; index < meterValuesTemplate.length; index++) { const connector = self.getConnector(connectorId); // SoC measurand - if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === MeterValueMeasurand.STATE_OF_CHARGE && self._getConfigurationKey('MeterValuesSampledData').value.includes(MeterValueMeasurand.STATE_OF_CHARGE)) { + if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === MeterValueMeasurand.STATE_OF_CHARGE && self._getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(MeterValueMeasurand.STATE_OF_CHARGE)) { meterValue.sampledValue.push({ ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.PERCENT }, ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context }, @@ -837,7 +848,7 @@ export default class ChargingStation { logger.error(`${self._logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : MeterValueMeasurand.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 === MeterValueMeasurand.VOLTAGE && self._getConfigurationKey('MeterValuesSampledData').value.includes(MeterValueMeasurand.VOLTAGE)) { + } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === MeterValueMeasurand.VOLTAGE && self._getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(MeterValueMeasurand.VOLTAGE)) { const voltageMeasurandValue = Utils.getRandomFloatRounded(self._getVoltageOut() + self._getVoltageOut() * 0.1, self._getVoltageOut() - self._getVoltageOut() * 0.1); meterValue.sampledValue.push({ ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.VOLT }, @@ -863,7 +874,7 @@ export default class ChargingStation { }); } // Power.Active.Import measurand - } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === MeterValueMeasurand.POWER_ACTIVE_IMPORT && self._getConfigurationKey('MeterValuesSampledData').value.includes(MeterValueMeasurand.POWER_ACTIVE_IMPORT)) { + } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === MeterValueMeasurand.POWER_ACTIVE_IMPORT && self._getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(MeterValueMeasurand.POWER_ACTIVE_IMPORT)) { // FIXME: factor out powerDivider checks if (Utils.isUndefined(self._stationInfo.powerDivider)) { const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`; @@ -923,7 +934,7 @@ export default class ChargingStation { }); } // Current.Import measurand - } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === MeterValueMeasurand.CURRENT_IMPORT && self._getConfigurationKey('MeterValuesSampledData').value.includes(MeterValueMeasurand.CURRENT_IMPORT)) { + } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === MeterValueMeasurand.CURRENT_IMPORT && self._getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(MeterValueMeasurand.CURRENT_IMPORT)) { // FIXME: factor out powerDivider checks if (Utils.isUndefined(self._stationInfo.powerDivider)) { const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`; @@ -1027,47 +1038,46 @@ export default class ChargingStation { transactionId: self.getConnector(connectorId).transactionId, meterValue: meterValue, }; - await self.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'MeterValues'); + await self.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, RequestCommand.METERVALUES); } catch (error) { - logger.error(self._logPrefix() + ' Send MeterValues error: %j', error); - throw error; + this.handleRequestError(RequestCommand.METERVALUES, error); } } - async sendError(messageId: string, err: Error | OCPPError, commandName: string): Promise { + async sendError(messageId: string, err: Error | OCPPError, commandName: RequestCommand | IncomingRequestCommand): Promise { // Check exception type: only OCPP error are accepted - const error = err instanceof OCPPError ? err : new OCPPError(Constants.OCPP_ERROR_INTERNAL_ERROR, err.message, err.stack && err.stack); + const error = err instanceof OCPPError ? err : new OCPPError(ErrorType.INTERNAL_ERROR, err.message, err.stack && err.stack); // Send error - return this.sendMessage(messageId, error, Constants.OCPP_JSON_CALL_ERROR_MESSAGE, commandName); + return this.sendMessage(messageId, error, MessageType.CALL_ERROR_MESSAGE, commandName); } - async sendMessage(messageId: string, commandParams, messageType = Constants.OCPP_JSON_CALL_RESULT_MESSAGE, commandName: string): Promise { + async sendMessage(messageId: string, commandParams, messageType: MessageType = MessageType.CALL_RESULT_MESSAGE, commandName: RequestCommand | IncomingRequestCommand): Promise { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; // Send a message through wsConnection return new Promise((resolve: (value?: any | PromiseLike) => void, reject: (reason?: any) => void) => { - let messageToSend; + let messageToSend: string; // Type of message switch (messageType) { // Request - case Constants.OCPP_JSON_CALL_MESSAGE: + case MessageType.CALL_MESSAGE: // Build request - this._requests[messageId] = [responseCallback, rejectCallback, commandParams]; + this._requests[messageId] = [responseCallback, rejectCallback, commandParams] as Request; messageToSend = JSON.stringify([messageType, messageId, commandName, commandParams]); break; // Response - case Constants.OCPP_JSON_CALL_RESULT_MESSAGE: + case MessageType.CALL_RESULT_MESSAGE: // Build response messageToSend = JSON.stringify([messageType, messageId, commandParams]); break; // Error Message - case Constants.OCPP_JSON_CALL_ERROR_MESSAGE: + case MessageType.CALL_ERROR_MESSAGE: // Build Error Message - messageToSend = JSON.stringify([messageType, messageId, commandParams.code ? commandParams.code : Constants.OCPP_ERROR_GENERIC_ERROR, commandParams.message ? commandParams.message : '', commandParams.details ? commandParams.details : {}]); + messageToSend = JSON.stringify([messageType, messageId, commandParams.code ? commandParams.code : ErrorType.GENERIC_ERROR, commandParams.message ? commandParams.message : '', commandParams.details ? commandParams.details : {}]); break; } // Check if wsConnection opened and charging station registered - if (this._isWebSocketOpen() && (this._isRegistered() || commandName === 'BootNotification')) { + if (this._isWebSocketOpen() && (this._isRegistered() || commandName === RequestCommand.BOOT_NOTIFICATION)) { if (this.getEnableStatistics()) { this._statistics.addMessage(commandName, messageType); } @@ -1078,7 +1088,7 @@ export default class ChargingStation { // Handle dups in buffer for (const message of this._messageQueue) { // Same message - if (JSON.stringify(messageToSend) === JSON.stringify(message)) { + if (messageToSend === message) { dups = true; break; } @@ -1088,15 +1098,15 @@ 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 : `WebSocket closed for message id '${messageId}' with content '${messageToSend}', message buffered`, commandParams.details ? commandParams.details : {})); + return rejectCallback(new OCPPError(commandParams.code ? commandParams.code : ErrorType.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) { + if (messageType === MessageType.CALL_RESULT_MESSAGE) { // Yes: send Ok resolve(); - } else if (messageType === Constants.OCPP_JSON_CALL_ERROR_MESSAGE) { + } else if (messageType === MessageType.CALL_ERROR_MESSAGE) { // Send timeout - setTimeout(() => rejectCallback(new OCPPError(commandParams.code ? commandParams.code : Constants.OCPP_ERROR_GENERIC_ERROR, commandParams.message ? commandParams.message : `Timeout for message id '${messageId}' with content '${messageToSend}'`, commandParams.details ? commandParams.details : {})), Constants.OCPP_SOCKET_TIMEOUT); + setTimeout(() => rejectCallback(new OCPPError(commandParams.code ? commandParams.code : ErrorType.GENERIC_ERROR, commandParams.message ? commandParams.message : `Timeout for message id '${messageId}' with content '${messageToSend}'`, commandParams.details ? commandParams.details : {})), Constants.OCPP_ERROR_TIMEOUT); } // Function that will receive the request's response @@ -1105,7 +1115,7 @@ export default class ChargingStation { self._statistics.addMessage(commandName, messageType); } // Send the response - await self.handleResponse(commandName, payload, requestPayload); + await self.handleResponse(commandName as RequestCommand, payload, requestPayload); resolve(payload); } @@ -1124,7 +1134,7 @@ export default class ChargingStation { }); } - async handleResponse(commandName: string, payload, requestPayload): Promise { + async handleResponse(commandName: RequestCommand, payload, requestPayload): Promise { const responseCallbackFn = 'handleResponse' + commandName; if (typeof this[responseCallbackFn] === 'function') { await this[responseCallbackFn](payload, requestPayload); @@ -1135,10 +1145,9 @@ export default class ChargingStation { handleResponseBootNotification(payload: BootNotificationResponse, requestPayload: BootNotificationRequest): void { if (payload.status === RegistrationStatus.ACCEPTED) { - this._heartbeatInterval = payload.interval * 1000; this._heartbeatSetInterval ? this._restartHeartbeat() : this._startHeartbeat(); - this._addConfigurationKey('HeartBeatInterval', payload.interval.toString()); - this._addConfigurationKey('HeartbeatInterval', payload.interval.toString(), false, false); + this._addConfigurationKey(StandardParametersKey.HeartBeatInterval, payload.interval.toString()); + this._addConfigurationKey(StandardParametersKey.HeartbeatInterval, payload.interval.toString(), false, false); this._hasStopped && (this._hasStopped = false); } else if (payload.status === RegistrationStatus.PENDING) { logger.info(this._logPrefix() + ' Charging station in pending state on the central server'); @@ -1189,7 +1198,7 @@ export default class ChargingStation { if (this._stationInfo.powerSharedByConnectors) { this._stationInfo.powerDivider++; } - const configuredMeterValueSampleInterval = this._getConfigurationKey('MeterValueSampleInterval'); + const configuredMeterValueSampleInterval = this._getConfigurationKey(StandardParametersKey.MeterValueSampleInterval); this._startMeterValues(connectorId, configuredMeterValueSampleInterval ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000 : 60000); } else { @@ -1235,7 +1244,7 @@ export default class ChargingStation { logger.debug(this._logPrefix() + ' Heartbeat response received: %j to Heartbeat request: %j', payload, requestPayload); } - async handleRequest(messageId: string, commandName: string, commandPayload): Promise { + async handleRequest(messageId: string, commandName: IncomingRequestCommand, commandPayload): Promise { let response; // Call if (typeof this['handleRequest' + commandName] === 'function') { @@ -1251,11 +1260,11 @@ export default class ChargingStation { } } else { // Throw exception - await this.sendError(messageId, new OCPPError(Constants.OCPP_ERROR_NOT_IMPLEMENTED, `${commandName} is not implemented`, {}), commandName); + await this.sendError(messageId, new OCPPError(ErrorType.NOT_IMPLEMENTED, `${commandName} is not implemented`, {}), commandName); throw new Error(`${commandName} is not implemented ${JSON.stringify(commandPayload, null, ' ')}`); } // Send response - await this.sendMessage(messageId, response, Constants.OCPP_JSON_CALL_RESULT_MESSAGE, commandName); + await this.sendMessage(messageId, response, MessageType.CALL_RESULT_MESSAGE, commandName); } // Simulate charging station restart @@ -1290,16 +1299,20 @@ export default class ChargingStation { return Constants.OCPP_RESPONSE_UNLOCKED; } - _getConfigurationKey(key: string, caseInsensitive = false): ConfigurationKey { - return this._configuration.configurationKey.find((configElement) => { + _getConfigurationKey(key: string | StandardParametersKey, caseInsensitive = false): ConfigurationKey { + const configurationKey: ConfigurationKey = this._configuration.configurationKey.find((configElement) => { if (caseInsensitive) { return configElement.key.toLowerCase() === key.toLowerCase(); } return configElement.key === key; }); + if (configurationKey && Utils.isUndefined(configurationKey.readonly)) { + configurationKey.readonly = false; + } + return configurationKey; } - _addConfigurationKey(key: string, value: string, readonly = false, visible = true, reboot = false): void { + _addConfigurationKey(key: string | StandardParametersKey, value: string, readonly = false, visible = true, reboot = false): void { const keyFound = this._getConfigurationKey(key); if (!keyFound) { this._configuration.configurationKey.push({ @@ -1309,19 +1322,23 @@ export default class ChargingStation { visible, reboot, }); + } else { + logger.error(`${this._logPrefix()} Trying to add an already existing configuration key: %j`, keyFound); } } - _setConfigurationKeyValue(key: string, value: string): void { + _setConfigurationKeyValue(key: string | StandardParametersKey, value: string): void { const keyFound = this._getConfigurationKey(key); if (keyFound) { const keyIndex = this._configuration.configurationKey.indexOf(keyFound); this._configuration.configurationKey[keyIndex].value = value; + } else { + logger.error(`${this._logPrefix()} Trying to set a value on a non existing configuration key: %j`, { key, value }); } } handleRequestGetConfiguration(commandPayload: GetConfigurationRequest): GetConfigurationResponse { - const configurationKey: ConfigurationKey[] = []; + const configurationKey: OCPPConfigurationKey[] = []; const unknownKey: string[] = []; if (Utils.isEmptyArray(commandPayload.key)) { for (const configuration of this._configuration.configurationKey) { @@ -1384,19 +1401,18 @@ export default class ChargingStation { valueChanged = true; } let triggerHeartbeatRestart = false; - if (keyToChange.key === 'HeartBeatInterval' && valueChanged) { - this._setConfigurationKeyValue('HeartbeatInterval', commandPayload.value); + if (keyToChange.key === StandardParametersKey.HeartBeatInterval && valueChanged) { + this._setConfigurationKeyValue(StandardParametersKey.HeartbeatInterval, commandPayload.value); triggerHeartbeatRestart = true; } - if (keyToChange.key === 'HeartbeatInterval' && valueChanged) { - this._setConfigurationKeyValue('HeartBeatInterval', commandPayload.value); + if (keyToChange.key === StandardParametersKey.HeartbeatInterval && valueChanged) { + this._setConfigurationKeyValue(StandardParametersKey.HeartBeatInterval, commandPayload.value); triggerHeartbeatRestart = true; } if (triggerHeartbeatRestart) { - this._heartbeatInterval = Utils.convertToInt(commandPayload.value) * 1000; this._restartHeartbeat(); } - if (keyToChange.key === 'WebSocketPingInterval' && valueChanged) { + if (keyToChange.key === StandardParametersKey.WebSocketPingInterval && valueChanged) { this._restartWebSocketPing(); } if (keyToChange.reboot) { @@ -1455,5 +1471,10 @@ export default class ChargingStation { logger.info(this._logPrefix() + ' Trying to remote stop a non existing transaction ' + transactionId.toString()); return Constants.OCPP_RESPONSE_REJECTED; } + + private handleRequestError(commandName: RequestCommand, error: Error) { + logger.error(this._logPrefix() + ' Send ' + commandName + ' error: %j', error); + throw error; + } }