X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=5bc0ec9ad24d8ae76f486f388eb86d03a2cad0b8;hb=6a4032b5d8f3cbaa18d3beddcdfe9d335c1cba90;hp=d9f548d71c47aa15ee55d4ebf32ccdbde5ffad91;hpb=db6ce8d86b3384fea4a55de8a543e7966f34685a;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index d9f548d7..5bc0ec9a 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -1,6 +1,7 @@ // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. import { createHash } from 'node:crypto'; +import { EventEmitter } from 'node:events'; import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { URL } from 'node:url'; @@ -21,6 +22,7 @@ import { import { buildConnectorsMap, checkChargingStation, + checkConfiguration, checkConnectorsConfiguration, checkStationInfoConnectorStatus, checkTemplate, @@ -36,11 +38,9 @@ import { getMaxNumberOfEvses, getNumberOfReservableConnectors, getPhaseRotationValue, - hasFeatureProfile, hasReservationExpired, initializeConnectorsMapStatus, propagateSerialNumber, - removeExpiredReservations, stationTemplateToStationInfo, warnTemplateKeysDeprecation, } from './Helpers'; @@ -49,13 +49,16 @@ import { OCPP16IncomingRequestService, OCPP16RequestService, OCPP16ResponseService, - OCPP16ServiceUtils, OCPP20IncomingRequestService, OCPP20RequestService, OCPP20ResponseService, type OCPPIncomingRequestService, type OCPPRequestService, - OCPPServiceUtils, + buildMeterValue, + buildStatusNotificationRequest, + buildTransactionEndMeterValue, + getMessageTypeString, + sendAndSetConnectorStatus, } from './ocpp'; import { SharedLRUCache } from './SharedLRUCache'; import { BaseError, OCPPError } from '../exception'; @@ -67,6 +70,7 @@ import { type BootNotificationResponse, type CachedRequest, type ChargingStationConfiguration, + ChargingStationEvents, type ChargingStationInfo, type ChargingStationOcppConfiguration, type ChargingStationTemplate, @@ -87,9 +91,7 @@ import { type HeartbeatResponse, type IncomingRequest, type IncomingRequestCommand, - type JsonType, MessageType, - type MeterValue, MeterValueMeasurand, type MeterValuesRequest, type MeterValuesResponse, @@ -111,7 +113,7 @@ import { type StopTransactionResponse, SupervisionUrlDistribution, SupportedFeatureProfiles, - VendorParametersKey, + Voltage, type WSError, WebSocketCloseEventStatusCode, type WsOptions, @@ -152,7 +154,7 @@ import { watchJsonFile, } from '../utils'; -export class ChargingStation { +export class ChargingStation extends EventEmitter { public readonly index: number; public readonly templateFile: string; public stationInfo!: ChargingStationInfo; @@ -161,7 +163,7 @@ export class ChargingStation { public idTagsCache: IdTagsCache; public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined; public ocppConfiguration!: ChargingStationOcppConfiguration | undefined; - public wsConnection!: WebSocket | null; + public wsConnection: WebSocket | null; public readonly connectors: Map; public readonly evses: Map; public readonly requests: Map; @@ -180,20 +182,20 @@ export class ChargingStation { private ocppIncomingRequestService!: OCPPIncomingRequestService; private readonly messageBuffer: Set; private configuredSupervisionUrl!: URL; - private wsConnectionRestarted: boolean; private autoReconnectRetryCount: number; private templateFileWatcher!: FSWatcher | undefined; private templateFileHash!: string; private readonly sharedLRUCache: SharedLRUCache; private webSocketPingSetInterval?: NodeJS.Timeout; private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel; - private reservationExpirationSetInterval?: NodeJS.Timeout; + private flushMessageBufferSetInterval?: NodeJS.Timeout; constructor(index: number, templateFile: string) { + super(); this.started = false; this.starting = false; this.stopping = false; - this.wsConnectionRestarted = false; + this.wsConnection = null; this.autoReconnectRetryCount = 0; this.index = index; this.templateFile = templateFile; @@ -205,6 +207,16 @@ export class ChargingStation { this.idTagsCache = IdTagsCache.getInstance(); this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this); + this.on(ChargingStationEvents.started, () => { + parentPort?.postMessage(buildStartedMessage(this)); + }); + this.on(ChargingStationEvents.stopped, () => { + parentPort?.postMessage(buildStoppedMessage(this)); + }); + this.on(ChargingStationEvents.updated, () => { + parentPort?.postMessage(buildUpdatedMessage(this)); + }); + this.initialize(); } @@ -215,43 +227,39 @@ export class ChargingStation { private get wsConnectionUrl(): URL { return new URL( `${ - this.getSupervisionUrlOcppConfiguration() && - isNotEmptyString(this.getSupervisionUrlOcppKey()) && - isNotEmptyString(getConfigurationKey(this, this.getSupervisionUrlOcppKey())?.value) - ? getConfigurationKey(this, this.getSupervisionUrlOcppKey())!.value + this.stationInfo?.supervisionUrlOcppConfiguration === true && + isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) && + isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)?.value) + ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)!.value : this.configuredSupervisionUrl.href }/${this.stationInfo.chargingStationId}`, ); } public logPrefix = (): string => { - return logPrefix( - ` ${ - (isNotEmptyString(this?.stationInfo?.chargingStationId) - ? this?.stationInfo?.chargingStationId - : getChargingStationId(this.index, this.getTemplateFromFile()!)) ?? - 'Error at building log prefix' - } |`, - ); + if (isNotEmptyString(this?.stationInfo?.chargingStationId)) { + return logPrefix(` ${this?.stationInfo?.chargingStationId} |`); + } + let stationTemplate: ChargingStationTemplate | undefined; + try { + stationTemplate = JSON.parse( + readFileSync(this.templateFile, 'utf8'), + ) as ChargingStationTemplate; + } catch { + stationTemplate = undefined; + } + return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`); }; public hasIdTags(): boolean { return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo)!)); } - public getEnableStatistics(): boolean { - return this.stationInfo.enableStatistics ?? false; - } - - public getRemoteAuthorization(): boolean { - return this.stationInfo.remoteAuthorization ?? true; - } - public getNumberOfPhases(stationInfo?: ChargingStationInfo): number { const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo; switch (this.getCurrentOutType(stationInfo)) { case CurrentType.AC: - return !isUndefined(localStationInfo.numberOfPhases) ? localStationInfo.numberOfPhases! : 3; + return localStationInfo.numberOfPhases ?? 3; case CurrentType.DC: return 0; } @@ -261,10 +269,6 @@ export class ChargingStation { return this?.wsConnection?.readyState === WebSocket.OPEN; } - public getRegistrationStatus(): RegistrationStatusEnumType | undefined { - return this?.bootNotificationResponse?.status; - } - public inUnknownState(): boolean { return isNullOrUndefined(this?.bootNotificationResponse?.status); } @@ -340,27 +344,6 @@ export class ChargingStation { return this.connectors.get(connectorId); } - public getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType { - return (stationInfo ?? this.stationInfo)?.currentOutType ?? CurrentType.AC; - } - - public getOcppStrictCompliance(): boolean { - return this.stationInfo?.ocppStrictCompliance ?? true; - } - - public getVoltageOut(stationInfo?: ChargingStationInfo): number { - const defaultVoltageOut = getDefaultVoltageOut( - this.getCurrentOutType(stationInfo), - this.logPrefix(), - this.templateFile, - ); - return (stationInfo ?? this.stationInfo).voltageOut ?? defaultVoltageOut; - } - - public getMaximumPower(stationInfo?: ChargingStationInfo): number { - return (stationInfo ?? this.stationInfo).maximumPower!; - } - public getConnectorMaximumAvailablePower(connectorId: number): number { let connectorAmperageLimitationPowerLimit: number | undefined; if ( @@ -368,17 +351,17 @@ export class ChargingStation { this.getAmperageLimitation()! < this.stationInfo.maximumAmperage! ) { connectorAmperageLimitationPowerLimit = - (this.getCurrentOutType() === CurrentType.AC + (this.stationInfo?.currentOutType === CurrentType.AC ? ACElectricUtils.powerTotal( this.getNumberOfPhases(), - this.getVoltageOut(), + this.stationInfo.voltageOut!, this.getAmperageLimitation()! * (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()), ) - : DCElectricUtils.power(this.getVoltageOut(), this.getAmperageLimitation()!)) / + : DCElectricUtils.power(this.stationInfo.voltageOut!, this.getAmperageLimitation()!)) / this.powerDivider; } - const connectorMaximumPower = this.getMaximumPower() / this.powerDivider; + const connectorMaximumPower = this.stationInfo.maximumPower! / this.powerDivider; const connectorChargingProfilesPowerLimit = getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId); return min( @@ -431,34 +414,6 @@ export class ChargingStation { return numberOfRunningTransactions; } - public getOutOfOrderEndMeterValues(): boolean { - return this.stationInfo?.outOfOrderEndMeterValues ?? false; - } - - public getBeginEndMeterValues(): boolean { - return this.stationInfo?.beginEndMeterValues ?? false; - } - - public getMeteringPerTransaction(): boolean { - return this.stationInfo?.meteringPerTransaction ?? true; - } - - public getTransactionDataMeterValues(): boolean { - return this.stationInfo?.transactionDataMeterValues ?? false; - } - - public getMainVoltageMeterValues(): boolean { - return this.stationInfo?.mainVoltageMeterValues ?? true; - } - - public getPhaseLineToLineVoltageMeterValues(): boolean { - return this.stationInfo?.phaseLineToLineVoltageMeterValues ?? false; - } - - public getCustomValueLimitationMeterValues(): boolean { - return this.stationInfo?.customValueLimitationMeterValues ?? true; - } - public getConnectorIdByTransactionId(transactionId: number): number | undefined { if (this.hasEvses) { for (const evseStatus of this.evses.values()) { @@ -496,7 +451,9 @@ export class ChargingStation { this, StandardParametersKey.AuthorizeRemoteTxRequests, ); - return authorizeRemoteTxRequests ? convertToBoolean(authorizeRemoteTxRequests.value) : false; + return authorizeRemoteTxRequests !== undefined + ? convertToBoolean(authorizeRemoteTxRequests.value) + : false; } public getLocalAuthListEnabled(): boolean { @@ -504,16 +461,18 @@ export class ChargingStation { this, StandardParametersKey.LocalAuthListEnabled, ); - return localAuthListEnabled ? convertToBoolean(localAuthListEnabled.value) : false; + return localAuthListEnabled !== undefined + ? convertToBoolean(localAuthListEnabled.value) + : false; } public getHeartbeatInterval(): number { const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval); - if (HeartbeatInterval) { + if (HeartbeatInterval !== undefined) { return secondsToMilliseconds(convertToInt(HeartbeatInterval.value)); } const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval); - if (HeartBeatInterval) { + if (HeartBeatInterval !== undefined) { return secondsToMilliseconds(convertToInt(HeartBeatInterval.value)); } this.stationInfo?.autoRegister === false && @@ -527,10 +486,10 @@ export class ChargingStation { public setSupervisionUrl(url: string): void { if ( - this.getSupervisionUrlOcppConfiguration() && - isNotEmptyString(this.getSupervisionUrlOcppKey()) + this.stationInfo?.supervisionUrlOcppConfiguration === true && + isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) ) { - setConfigurationKeyValue(this, this.getSupervisionUrlOcppKey(), url); + setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey!, url); } else { this.stationInfo.supervisionUrls = url; this.saveStationInfo(); @@ -539,7 +498,7 @@ export class ChargingStation { } public startHeartbeat(): void { - if (this.getHeartbeatInterval() > 0 && !this.heartbeatSetInterval) { + if (this.getHeartbeatInterval() > 0 && this.heartbeatSetInterval === undefined) { this.heartbeatSetInterval = setInterval(() => { this.ocppRequestService .requestHandler(this, RequestCommand.HEARTBEAT) @@ -555,7 +514,7 @@ export class ChargingStation { this.getHeartbeatInterval(), )}`, ); - } else if (this.heartbeatSetInterval) { + } else if (this.heartbeatSetInterval !== undefined) { logger.info( `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds( this.getHeartbeatInterval(), @@ -612,8 +571,7 @@ export class ChargingStation { } if (interval > 0) { this.getConnectorStatus(connectorId)!.transactionSetInterval = setInterval(() => { - // FIXME: Implement OCPP version agnostic helpers - const meterValue: MeterValue = OCPP16ServiceUtils.buildMeterValue( + const meterValue = buildMeterValue( this, connectorId, this.getConnectorStatus(connectorId)!.transactionId!, @@ -646,7 +604,7 @@ export class ChargingStation { } public stopMeterValues(connectorId: number) { - if (this.getConnectorStatus(connectorId)?.transactionSetInterval) { + if (this.getConnectorStatus(connectorId)?.transactionSetInterval !== undefined) { clearInterval(this.getConnectorStatus(connectorId)?.transactionSetInterval); } } @@ -655,12 +613,9 @@ export class ChargingStation { if (this.started === false) { if (this.starting === false) { this.starting = true; - if (this.getEnableStatistics() === true) { + if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.start(); } - if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) { - this.startReservationExpirationSetInterval(); - } this.openWSConnection(); // Monitor charging station template file this.templateFileWatcher = watchJsonFile( @@ -683,10 +638,10 @@ export class ChargingStation { // Restart the ATG this.stopAutomaticTransactionGenerator(); delete this.automaticTransactionGeneratorConfiguration; - if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) { + if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) { this.startAutomaticTransactionGenerator(); } - if (this.getEnableStatistics() === true) { + if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.restart(); } else { this.performanceStatistics?.stop(); @@ -702,7 +657,7 @@ export class ChargingStation { }, ); this.started = true; - parentPort?.postMessage(buildStartedMessage(this)); + this.emit(ChargingStationEvents.started); this.starting = false; } else { logger.warn(`${this.logPrefix()} Charging station is already starting...`); @@ -712,25 +667,22 @@ export class ChargingStation { } } - public async stop(reason?: StopTransactionReason): Promise { + public async stop(reason?: StopTransactionReason, stopTransactions?: boolean): Promise { if (this.started === true) { if (this.stopping === false) { this.stopping = true; - await this.stopMessageSequence(reason); + await this.stopMessageSequence(reason, stopTransactions); this.closeWSConnection(); - if (this.getEnableStatistics() === true) { + if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.stop(); } - if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) { - this.stopReservationExpirationSetInterval(); - } this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash); this.templateFileWatcher?.close(); this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash); delete this.bootNotificationResponse; this.started = false; this.saveConfiguration(); - parentPort?.postMessage(buildStoppedMessage(this)); + this.emit(ChargingStationEvents.stopped); this.stopping = false; } else { logger.warn(`${this.logPrefix()} Charging station is already stopping...`); @@ -748,13 +700,14 @@ export class ChargingStation { } public saveOcppConfiguration(): void { - if (this.getOcppPersistentConfiguration()) { + if (this.stationInfo?.ocppPersistentConfiguration === true) { this.saveConfiguration(); } } public bufferMessage(message: string): void { this.messageBuffer.add(message); + this.setIntervalFlushMessageBuffer(); } public openWSConnection( @@ -796,7 +749,7 @@ export class ChargingStation { this.wsConnection = new WebSocket( this.wsConnectionUrl, - `ocpp${this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16}`, + `ocpp${this.stationInfo?.ocppVersion}`, options, ); @@ -835,17 +788,17 @@ export class ChargingStation { let automaticTransactionGeneratorConfiguration: | AutomaticTransactionGeneratorConfiguration | undefined; - const automaticTransactionGeneratorConfigurationFromFile = - this.getConfigurationFromFile()?.automaticTransactionGenerator; + const stationTemplate = this.getTemplateFromFile(); + const stationConfiguration = this.getConfigurationFromFile(); if ( - this.getAutomaticTransactionGeneratorPersistentConfiguration() && - automaticTransactionGeneratorConfigurationFromFile + this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true && + stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash && + stationConfiguration?.automaticTransactionGenerator ) { automaticTransactionGeneratorConfiguration = - automaticTransactionGeneratorConfigurationFromFile; + stationConfiguration?.automaticTransactionGenerator; } else { - automaticTransactionGeneratorConfiguration = - this.getTemplateFromFile()?.AutomaticTransactionGenerator; + automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator; } this.automaticTransactionGeneratorConfiguration = { ...Constants.DEFAULT_ATG_CONFIGURATION, @@ -869,7 +822,7 @@ export class ChargingStation { this.automaticTransactionGenerator?.start(); } this.saveAutomaticTransactionGeneratorConfiguration(); - parentPort?.postMessage(buildUpdatedMessage(this)); + this.emit(ChargingStationEvents.updated); } public stopAutomaticTransactionGenerator(connectorIds?: number[]): void { @@ -881,21 +834,20 @@ export class ChargingStation { this.automaticTransactionGenerator?.stop(); } this.saveAutomaticTransactionGeneratorConfiguration(); - parentPort?.postMessage(buildUpdatedMessage(this)); + this.emit(ChargingStationEvents.updated); } public async stopTransactionOnConnector( connectorId: number, - reason = StopTransactionReason.NONE, + reason?: StopTransactionReason, ): Promise { const transactionId = this.getConnectorStatus(connectorId)?.transactionId; if ( - this.getBeginEndMeterValues() === true && - this.getOcppStrictCompliance() === true && - this.getOutOfOrderEndMeterValues() === false + this.stationInfo?.beginEndMeterValues === true && + this.stationInfo?.ocppStrictCompliance === true && + this.stationInfo?.outOfOrderEndMeterValues === false ) { - // FIXME: Implement OCPP version agnostic helpers - const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue( + const transactionEndMeterValue = buildTransactionEndMeterValue( this, connectorId, this.getEnergyActiveImportRegisterByTransactionId(transactionId!), @@ -916,7 +868,7 @@ export class ChargingStation { { transactionId, meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId!, true), - reason, + ...(isNullOrUndefined(reason) && { reason }), }, ); } @@ -929,14 +881,11 @@ export class ChargingStation { public async addReservation(reservation: Reservation): Promise { const reservationFound = this.getReservationBy('reservationId', reservation.reservationId); - if (!isUndefined(reservationFound)) { - await this.removeReservation( - reservationFound!, - ReservationTerminationReason.REPLACE_EXISTING, - ); + if (reservationFound !== undefined) { + await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING); } this.getConnectorStatus(reservation.connectorId)!.reservation = reservation; - await OCPPServiceUtils.sendAndSetConnectorStatus( + await sendAndSetConnectorStatus( this, reservation.connectorId, ConnectorStatusEnum.Reserved, @@ -958,7 +907,7 @@ export class ChargingStation { case ReservationTerminationReason.RESERVATION_CANCELED: case ReservationTerminationReason.REPLACE_EXISTING: case ReservationTerminationReason.EXPIRED: - await OCPPServiceUtils.sendAndSetConnectorStatus( + await sendAndSetConnectorStatus( this, reservation.connectorId, ConnectorStatusEnum.Available, @@ -1018,31 +967,26 @@ export class ChargingStation { return false; } - private startReservationExpirationSetInterval(customInterval?: number): void { - const interval = customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_INTERVAL; - if (interval > 0) { - logger.info( - `${this.logPrefix()} Reservation expiration date checks started every ${formatDurationMilliSeconds( - interval, - )}`, - ); - this.reservationExpirationSetInterval = setInterval((): void => { - removeExpiredReservations(this).catch(Constants.EMPTY_FUNCTION); - }, interval); + private setIntervalFlushMessageBuffer(): void { + if (this.flushMessageBufferSetInterval === undefined) { + this.flushMessageBufferSetInterval = setInterval(() => { + if (this.isWebSocketConnectionOpened() === true && this.inAcceptedState() === true) { + this.flushMessageBuffer(); + } + if (this.messageBuffer.size === 0) { + this.clearIntervalFlushMessageBuffer(); + } + }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL); } } - private stopReservationExpirationSetInterval(): void { - if (this.reservationExpirationSetInterval) { - clearInterval(this.reservationExpirationSetInterval); + private clearIntervalFlushMessageBuffer() { + if (this.flushMessageBufferSetInterval !== undefined) { + clearInterval(this.flushMessageBufferSetInterval); + delete this.flushMessageBufferSetInterval; } } - // private restartReservationExpiryDateSetInterval(): void { - // this.stopReservationExpirationSetInterval(); - // this.startReservationExpirationSetInterval(); - // } - private getNumberOfReservableConnectors(): number { let numberOfReservableConnectors = 0; if (this.hasEvses) { @@ -1077,26 +1021,28 @@ export class ChargingStation { [, , commandName] = JSON.parse(message) as OutgoingRequest; beginId = PerformanceStatistics.beginMeasure(commandName); } - this.wsConnection?.send(message); - isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!); - logger.debug( - `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString( - messageType, - )} payload sent: ${message}`, - ); - this.messageBuffer.delete(message); + this.wsConnection?.send(message, (error?: Error) => { + isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!); + if (isNullOrUndefined(error)) { + logger.debug( + `${this.logPrefix()} >> Buffered ${getMessageTypeString( + messageType, + )} OCPP message sent '${JSON.stringify(message)}'`, + ); + this.messageBuffer.delete(message); + } else { + logger.debug( + `${this.logPrefix()} >> Buffered ${getMessageTypeString( + messageType, + )} OCPP message '${JSON.stringify(message)}' send failed:`, + error, + ); + } + }); } } } - private getSupervisionUrlOcppConfiguration(): boolean { - return this.stationInfo.supervisionUrlOcppConfiguration ?? false; - } - - private getSupervisionUrlOcppKey(): string { - return this.stationInfo.supervisionUrlOcppKey ?? VendorParametersKey.ConnectionUrl; - } - private getTemplateFromFile(): ChargingStationTemplate | undefined { let template: ChargingStationTemplate | undefined; try { @@ -1137,6 +1083,7 @@ export class ChargingStation { stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate); stationInfo.ocppVersion = stationTemplate?.ocppVersion ?? OCPPVersion.VERSION_16; createSerialNumber(stationTemplate, stationInfo); + stationInfo.voltageOut = this.getVoltageOut(stationInfo); if (isNotEmptyArray(stationTemplate?.power)) { stationTemplate.power = stationTemplate.power as number[]; const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length); @@ -1151,6 +1098,7 @@ export class ChargingStation { ? stationTemplate.power * 1000 : stationTemplate.power; } + stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo); stationInfo.firmwareVersionPattern = stationTemplate?.firmwareVersionPattern ?? Constants.SEMVER_PATTERN; if ( @@ -1175,13 +1123,14 @@ export class ChargingStation { stationInfo.resetTime = !isNullOrUndefined(stationTemplate?.resetTime) ? secondsToMilliseconds(stationTemplate.resetTime!) : Constants.CHARGING_STATION_DEFAULT_RESET_TIME; - stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo); return stationInfo; } - private getStationInfoFromFile(): ChargingStationInfo | undefined { + private getStationInfoFromFile( + stationInfoPersistentConfiguration = true, + ): ChargingStationInfo | undefined { let stationInfo: ChargingStationInfo | undefined; - if (this.getStationInfoPersistentConfiguration()) { + if (stationInfoPersistentConfiguration === true) { stationInfo = this.getConfigurationFromFile()?.stationInfo; if (stationInfo) { delete stationInfo?.infoHash; @@ -1191,13 +1140,16 @@ export class ChargingStation { } private getStationInfo(): ChargingStationInfo { + const defaultStationInfo = Constants.DEFAULT_STATION_INFO; const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate(); - const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile(); + const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile( + stationInfoFromTemplate?.stationInfoPersistentConfiguration, + ); // Priority: // 1. charging station info from template // 2. charging station info from configuration file if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) { - return stationInfoFromFile!; + return { ...defaultStationInfo, ...stationInfoFromFile! }; } stationInfoFromFile && propagateSerialNumber( @@ -1205,28 +1157,16 @@ export class ChargingStation { stationInfoFromFile, stationInfoFromTemplate, ); - return stationInfoFromTemplate; + return { ...defaultStationInfo, ...stationInfoFromTemplate }; } private saveStationInfo(): void { - if (this.getStationInfoPersistentConfiguration()) { + if (this.stationInfo?.stationInfoPersistentConfiguration === true) { this.saveConfiguration(); } } - private getOcppPersistentConfiguration(): boolean { - return this.stationInfo?.ocppPersistentConfiguration ?? true; - } - - private getStationInfoPersistentConfiguration(): boolean { - return this.stationInfo?.stationInfoPersistentConfiguration ?? true; - } - - private getAutomaticTransactionGeneratorPersistentConfiguration(): boolean { - return this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration ?? true; - } - - private handleUnsupportedVersion(version: OCPPVersion) { + private handleUnsupportedVersion(version: OCPPVersion | undefined) { const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`; logger.error(`${this.logPrefix()} ${errorMsg}`); throw new BaseError(errorMsg); @@ -1239,13 +1179,14 @@ export class ChargingStation { dirname(this.templateFile.replace('station-templates', 'configurations')), `${getHashId(this.index, stationTemplate)}.json`, ); - const chargingStationConfiguration = this.getConfigurationFromFile(); + const stationConfiguration = this.getConfigurationFromFile(); if ( - chargingStationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash && + stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash && // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (chargingStationConfiguration?.connectorsStatus || chargingStationConfiguration?.evsesStatus) + (stationConfiguration?.connectorsStatus || stationConfiguration?.evsesStatus) ) { - this.initializeConnectorsOrEvsesFromFile(chargingStationConfiguration); + checkConfiguration(stationConfiguration, this.logPrefix(), this.configurationFile); + this.initializeConnectorsOrEvsesFromFile(stationConfiguration); } else { this.initializeConnectorsOrEvsesFromTemplate(stationTemplate); } @@ -1258,19 +1199,21 @@ export class ChargingStation { const patternGroup: number | undefined = this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ?? this.stationInfo.firmwareVersion?.split('.').length; - const match = this.stationInfo - .firmwareVersion!.match(new RegExp(this.stationInfo.firmwareVersionPattern!))! - .slice(1, patternGroup! + 1); - const patchLevelIndex = match.length - 1; - match[patchLevelIndex] = ( - convertToInt(match[patchLevelIndex]) + - this.stationInfo.firmwareUpgrade!.versionUpgrade!.step! - ).toString(); - this.stationInfo.firmwareVersion = match?.join('.'); + const match = new RegExp(this.stationInfo.firmwareVersionPattern!) + .exec(this.stationInfo.firmwareVersion!) + ?.slice(1, patternGroup! + 1); + if (!isNullOrUndefined(match)) { + const patchLevelIndex = match!.length - 1; + match![patchLevelIndex] = ( + convertToInt(match![patchLevelIndex]) + + this.stationInfo.firmwareUpgrade!.versionUpgrade!.step! + ).toString(); + this.stationInfo.firmwareVersion = match!.join('.'); + } } this.saveStationInfo(); this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl(); - if (this.getEnableStatistics() === true) { + if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics = PerformanceStatistics.getInstance( this.stationInfo.hashId, this.stationInfo.chargingStationId!, @@ -1283,6 +1226,11 @@ export class ChargingStation { this.ocppConfiguration = this.getOcppConfiguration(); this.initializeOcppConfiguration(); this.initializeOcppServices(); + this.once(ChargingStationEvents.accepted, () => { + this.startMessageSequence().catch((error) => { + logger.error(`${this.logPrefix()} Error while starting the message sequence:`, error); + }); + }); if (this.stationInfo?.autoRegister === true) { this.bootNotificationResponse = { currentTime: new Date(), @@ -1293,7 +1241,7 @@ export class ChargingStation { } private initializeOcppServices(): void { - const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16; + const ocppVersion = this.stationInfo?.ocppVersion; switch (ocppVersion) { case OCPPVersion.VERSION_16: this.ocppIncomingRequestService = @@ -1317,33 +1265,33 @@ export class ChargingStation { } private initializeOcppConfiguration(): void { - if (!getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)) { + if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.HeartbeatInterval))) { addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0'); } - if (!getConfigurationKey(this, StandardParametersKey.HeartBeatInterval)) { + if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.HeartBeatInterval))) { addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', { visible: false }); } if ( - this.getSupervisionUrlOcppConfiguration() && - isNotEmptyString(this.getSupervisionUrlOcppKey()) && - !getConfigurationKey(this, this.getSupervisionUrlOcppKey()) + this.stationInfo?.supervisionUrlOcppConfiguration === true && + isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) && + isNullOrUndefined(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)) ) { addConfigurationKey( this, - this.getSupervisionUrlOcppKey(), + this.stationInfo.supervisionUrlOcppKey!, this.configuredSupervisionUrl.href, { reboot: true }, ); } else if ( - !this.getSupervisionUrlOcppConfiguration() && - isNotEmptyString(this.getSupervisionUrlOcppKey()) && - getConfigurationKey(this, this.getSupervisionUrlOcppKey()) + this.stationInfo?.supervisionUrlOcppConfiguration === false && + isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) && + !isNullOrUndefined(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)) ) { - deleteConfigurationKey(this, this.getSupervisionUrlOcppKey(), { save: false }); + deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!, { save: false }); } if ( isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) && - !getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!) + isNullOrUndefined(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)) ) { addConfigurationKey( this, @@ -1353,7 +1301,9 @@ export class ChargingStation { ).toString(), ); } - if (!getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)) { + if ( + isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)) + ) { addConfigurationKey( this, StandardParametersKey.SupportedFeatureProfiles, @@ -1367,14 +1317,18 @@ export class ChargingStation { { readonly: true }, { overwrite: true }, ); - if (!getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData)) { + if ( + isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData)) + ) { addConfigurationKey( this, StandardParametersKey.MeterValuesSampledData, MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, ); } - if (!getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation)) { + if ( + isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation)) + ) { const connectorsPhaseRotation: string[] = []; if (this.hasEvses) { for (const evseStatus of this.evses.values()) { @@ -1397,18 +1351,20 @@ export class ChargingStation { connectorsPhaseRotation.toString(), ); } - if (!getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests)) { + if ( + isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests)) + ) { addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true'); } if ( - !getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) && + isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled)) && getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)?.value?.includes( SupportedFeatureProfiles.LocalAuthListManagement, ) ) { addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false'); } - if (!getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)) { + if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut))) { addConfigurationKey( this, StandardParametersKey.ConnectionTimeOut, @@ -1630,7 +1586,7 @@ export class ChargingStation { } private saveAutomaticTransactionGeneratorConfiguration(): void { - if (this.getAutomaticTransactionGeneratorPersistentConfiguration()) { + if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true) { this.saveConfiguration(); } } @@ -1652,13 +1608,16 @@ export class ChargingStation { let configurationData: ChargingStationConfiguration = this.getConfigurationFromFile() ? cloneObject(this.getConfigurationFromFile()!) : {}; - if (this.getStationInfoPersistentConfiguration() && this.stationInfo) { + if (this.stationInfo?.stationInfoPersistentConfiguration === true && this.stationInfo) { configurationData.stationInfo = this.stationInfo; } else { delete configurationData.stationInfo; } - if (this.getOcppPersistentConfiguration() && this.ocppConfiguration?.configurationKey) { - configurationData.configurationKey = this.ocppConfiguration.configurationKey; + if ( + this.stationInfo?.ocppPersistentConfiguration === true && + Array.isArray(this.ocppConfiguration?.configurationKey) + ) { + configurationData.configurationKey = this.ocppConfiguration?.configurationKey; } else { delete configurationData.configurationKey; } @@ -1667,7 +1626,7 @@ export class ChargingStation { buildChargingStationAutomaticTransactionGeneratorConfiguration(this), ); if ( - !this.getAutomaticTransactionGeneratorPersistentConfiguration() || + !this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration || !this.getAutomaticTransactionGeneratorConfiguration() ) { delete configurationData.automaticTransactionGenerator; @@ -1689,6 +1648,10 @@ export class ChargingStation { stationInfo: configurationData.stationInfo, configurationKey: configurationData.configurationKey, automaticTransactionGenerator: configurationData.automaticTransactionGenerator, + ...(this.connectors.size > 0 && { + connectorsStatus: configurationData.connectorsStatus, + }), + ...(this.evses.size > 0 && { evsesStatus: configurationData.evsesStatus }), } as ChargingStationConfiguration), ) .digest('hex'); @@ -1699,7 +1662,7 @@ export class ChargingStation { const beginId = PerformanceStatistics.beginMeasure(measureId); writeFileSync( this.configurationFile, - JSON.stringify(configurationData, null, 2), + JSON.stringify(configurationData, undefined, 2), 'utf8', ); PerformanceStatistics.endMeasure(measureId, beginId); @@ -1742,7 +1705,7 @@ export class ChargingStation { private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined { const configurationKey = this.getConfigurationFromFile()?.configurationKey; - if (this.getOcppPersistentConfiguration() === true && configurationKey) { + if (this.stationInfo?.ocppPersistentConfiguration === true && Array.isArray(configurationKey)) { return { configurationKey }; } return undefined; @@ -1762,9 +1725,9 @@ export class ChargingStation { logger.info( `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`, ); + let registrationRetryCount = 0; if (this.isRegistered() === false) { // Send BootNotification - let registrationRetryCount = 0; do { this.bootNotificationResponse = await this.ocppRequestService.requestHandler< BootNotificationRequest, @@ -1773,7 +1736,7 @@ export class ChargingStation { skipBufferingOnError: true, }); if (this.isRegistered() === false) { - this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount; + this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount; await sleep( this?.bootNotificationResponse?.interval ? secondsToMilliseconds(this.bootNotificationResponse.interval) @@ -1782,22 +1745,23 @@ export class ChargingStation { } } while ( this.isRegistered() === false && - (registrationRetryCount <= this.getRegistrationMaxRetries()! || - this.getRegistrationMaxRetries() === -1) + (registrationRetryCount <= this.stationInfo.registrationMaxRetries! || + this.stationInfo?.registrationMaxRetries === -1) ); } if (this.isRegistered() === true) { + this.emit(ChargingStationEvents.registered); if (this.inAcceptedState() === true) { - await this.startMessageSequence(); + this.emit(ChargingStationEvents.accepted); } } else { logger.error( - `${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`, + `${this.logPrefix()} Registration failure: maximum retries reached (${registrationRetryCount}) or retry disabled (${this + .stationInfo?.registrationMaxRetries})`, ); } - this.wsConnectionRestarted = false; this.autoReconnectRetryCount = 0; - parentPort?.postMessage(buildUpdatedMessage(this)); + this.emit(ChargingStationEvents.updated); } else { logger.warn( `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`, @@ -1805,7 +1769,7 @@ export class ChargingStation { } } - private async onClose(code: number, reason: Buffer): Promise { + private async onClose(code: WebSocketCloseEventStatusCode, reason: Buffer): Promise { switch (code) { // Normal close case WebSocketCloseEventStatusCode.CLOSE_NORMAL: @@ -1827,7 +1791,7 @@ export class ChargingStation { this.started === true && (await this.reconnect()); break; } - parentPort?.postMessage(buildUpdatedMessage(this)); + this.emit(ChargingStationEvents.updated); } private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined { @@ -1837,17 +1801,17 @@ export class ChargingStation { } throw new OCPPError( ErrorType.PROTOCOL_ERROR, - `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString( + `Cached request for message id ${messageId} ${getMessageTypeString( messageType, )} is not an array`, undefined, - cachedRequest as JsonType, + cachedRequest, ); } private async handleIncomingMessage(request: IncomingRequest): Promise { const [messageType, messageId, commandName, commandPayload] = request; - if (this.getEnableStatistics() === true) { + if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.addRequestStatistic(commandName, messageType); } logger.debug( @@ -1862,6 +1826,7 @@ export class ChargingStation { commandName, commandPayload, ); + this.emit(ChargingStationEvents.updated); } private handleResponseMessage(response: Response): void { @@ -1910,7 +1875,7 @@ export class ChargingStation { private async onMessage(data: RawData): Promise { let request: IncomingRequest | Response | ErrorResponse | undefined; - let messageType: number | undefined; + let messageType: MessageType | undefined; let errorMsg: string; try { // eslint-disable-next-line @typescript-eslint/no-base-to-string @@ -1938,7 +1903,6 @@ export class ChargingStation { logger.error(`${this.logPrefix()} ${errorMsg}`); throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg); } - parentPort?.postMessage(buildUpdatedMessage(this)); } else { throw new OCPPError( ErrorType.PROTOCOL_ERROR, @@ -2009,7 +1973,7 @@ export class ChargingStation { } private getEnergyActiveImportRegister(connectorStatus: ConnectorStatus, rounded = false): number { - if (this.getMeteringPerTransaction() === true) { + if (this.stationInfo?.meteringPerTransaction === true) { return ( (rounded === true ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue!) @@ -2027,7 +1991,7 @@ export class ChargingStation { return stationTemplate?.useConnectorId0 ?? true; } - private async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise { + private async stopRunningTransactions(reason?: StopTransactionReason): Promise { if (this.hasEvses) { for (const [evseId, evseStatus] of this.evses) { if (evseId === 0) { @@ -2050,35 +2014,25 @@ export class ChargingStation { // 0 for disabling private getConnectionTimeout(): number { - if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)) { - return ( - parseInt(getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)!.value!) ?? - Constants.DEFAULT_CONNECTION_TIMEOUT + if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) !== undefined) { + return convertToInt( + getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)!.value! ?? + Constants.DEFAULT_CONNECTION_TIMEOUT, ); } return Constants.DEFAULT_CONNECTION_TIMEOUT; } - // -1 for unlimited, 0 for disabling - private getAutoReconnectMaxRetries(): number | undefined { - return this.stationInfo.autoReconnectMaxRetries ?? -1; - } - - // -1 for unlimited, 0 for disabling - private getRegistrationMaxRetries(): number | undefined { - return this.stationInfo.registrationMaxRetries ?? -1; - } - private getPowerDivider(): number { let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors(); - if (this.stationInfo?.powerSharedByConnectors) { + if (this.stationInfo?.powerSharedByConnectors === true) { powerDivider = this.getNumberOfRunningTransactions(); } return powerDivider; } - private getMaximumAmperage(stationInfo: ChargingStationInfo): number | undefined { - const maximumPower = this.getMaximumPower(stationInfo); + private getMaximumAmperage(stationInfo?: ChargingStationInfo): number | undefined { + const maximumPower = (stationInfo ?? this.stationInfo).maximumPower!; switch (this.getCurrentOutType(stationInfo)) { case CurrentType.AC: return ACElectricUtils.amperagePerPhaseFromPower( @@ -2091,10 +2045,21 @@ export class ChargingStation { } } + private getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType { + return (stationInfo ?? this.stationInfo).currentOutType ?? CurrentType.AC; + } + + private getVoltageOut(stationInfo?: ChargingStationInfo): Voltage { + return ( + (stationInfo ?? this.stationInfo).voltageOut ?? + getDefaultVoltageOut(this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile) + ); + } + private getAmperageLimitation(): number | undefined { if ( isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) && - getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!) + getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!) !== undefined ) { return ( convertToInt( @@ -2123,12 +2088,7 @@ export class ChargingStation { if (evseId > 0) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { const connectorBootStatus = getBootConnectorStatus(this, connectorId, connectorStatus); - await OCPPServiceUtils.sendAndSetConnectorStatus( - this, - connectorId, - connectorBootStatus, - evseId, - ); + await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus, evseId); } } } @@ -2140,11 +2100,11 @@ export class ChargingStation { connectorId, this.getConnectorStatus(connectorId)!, ); - await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorBootStatus); + await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus); } } } - if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) { + if (this.stationInfo.firmwareStatus === FirmwareStatus.Installing) { await this.ocppRequestService.requestHandler< FirmwareStatusNotificationRequest, FirmwareStatusNotificationResponse @@ -2155,25 +2115,26 @@ export class ChargingStation { } // Start the ATG - if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) { + if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) { this.startAutomaticTransactionGenerator(); } - this.wsConnectionRestarted === true && this.flushMessageBuffer(); + this.flushMessageBuffer(); } private async stopMessageSequence( - reason: StopTransactionReason = StopTransactionReason.NONE, + reason?: StopTransactionReason, + stopTransactions = this.stationInfo?.stopTransactionsOnStopped, ): Promise { // Stop WebSocket ping this.stopWebSocketPing(); // Stop heartbeat this.stopHeartbeat(); - // Stop ongoing transactions + // Stop the ATG if (this.automaticTransactionGenerator?.started === true) { this.stopAutomaticTransactionGenerator(); - } else { - await this.stopRunningTransactions(reason); } + // Stop ongoing transactions + stopTransactions && (await this.stopRunningTransactions(reason)); if (this.hasEvses) { for (const [evseId, evseStatus] of this.evses) { if (evseId > 0) { @@ -2184,7 +2145,7 @@ export class ChargingStation { >( this, RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( + buildStatusNotificationRequest( this, connectorId, ConnectorStatusEnum.Unavailable, @@ -2204,11 +2165,7 @@ export class ChargingStation { >( this, RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( - this, - connectorId, - ConnectorStatusEnum.Unavailable, - ), + buildStatusNotificationRequest(this, connectorId, ConnectorStatusEnum.Unavailable), ); delete this.getConnectorStatus(connectorId)?.status; } @@ -2217,13 +2174,13 @@ export class ChargingStation { } private startWebSocketPing(): void { - const webSocketPingInterval: number = getConfigurationKey( - this, - StandardParametersKey.WebSocketPingInterval, - ) - ? convertToInt(getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value) - : 0; - if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) { + const webSocketPingInterval: number = + getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) !== undefined + ? convertToInt( + getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value, + ) + : 0; + if (webSocketPingInterval > 0 && this.webSocketPingSetInterval === undefined) { this.webSocketPingSetInterval = setInterval(() => { if (this.isWebSocketConnectionOpened() === true) { this.wsConnection?.ping(); @@ -2234,7 +2191,7 @@ export class ChargingStation { webSocketPingInterval, )}`, ); - } else if (this.webSocketPingSetInterval) { + } else if (this.webSocketPingSetInterval !== undefined) { logger.info( `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds( webSocketPingInterval, @@ -2248,7 +2205,7 @@ export class ChargingStation { } private stopWebSocketPing(): void { - if (this.webSocketPingSetInterval) { + if (this.webSocketPingSetInterval !== undefined) { clearInterval(this.webSocketPingSetInterval); delete this.webSocketPingSetInterval; } @@ -2293,7 +2250,7 @@ export class ChargingStation { } private stopHeartbeat(): void { - if (this.heartbeatSetInterval) { + if (this.heartbeatSetInterval !== undefined) { clearInterval(this.heartbeatSetInterval); delete this.heartbeatSetInterval; } @@ -2306,10 +2263,6 @@ export class ChargingStation { } } - private getReconnectExponentialDelay(): boolean { - return this.stationInfo?.reconnectExponentialDelay ?? false; - } - private async reconnect(): Promise { // Stop WebSocket ping this.stopWebSocketPing(); @@ -2320,13 +2273,14 @@ export class ChargingStation { this.stopAutomaticTransactionGenerator(); } if ( - this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries()! || - this.getAutoReconnectMaxRetries() === -1 + this.autoReconnectRetryCount < this.stationInfo.autoReconnectMaxRetries! || + this.stationInfo?.autoReconnectMaxRetries === -1 ) { ++this.autoReconnectRetryCount; - const reconnectDelay = this.getReconnectExponentialDelay() - ? exponentialDelay(this.autoReconnectRetryCount) - : secondsToMilliseconds(this.getConnectionTimeout()); + const reconnectDelay = + this.stationInfo?.reconnectExponentialDelay === true + ? exponentialDelay(this.autoReconnectRetryCount) + : secondsToMilliseconds(this.getConnectionTimeout()); const reconnectDelayWithdraw = 1000; const reconnectTimeout = reconnectDelay && reconnectDelay - reconnectDelayWithdraw > 0 @@ -2348,12 +2302,11 @@ export class ChargingStation { }, { closeOpened: true }, ); - this.wsConnectionRestarted = true; - } else if (this.getAutoReconnectMaxRetries() !== -1) { + } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) { logger.error( `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${ this.autoReconnectRetryCount - }) or retries disabled (${this.getAutoReconnectMaxRetries()})`, + }) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries})`, ); } }