X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=726a2ce9fc63ee06253dc379d003930f9a791888;hb=be245fdab36274873e0a9651589cebd097548076;hp=5d6ee757a7009f7dea6b3f31ea999eedc4e32bab;hpb=12f26d4a81773cf8ede8bad4255e36aadf10bce0;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 5d6ee757..726a2ce9 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -9,31 +9,24 @@ import { parentPort } from 'node:worker_threads'; import merge from 'just-merge'; import WebSocket, { type RawData } from 'ws'; +import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator'; +import { ChargingStationWorkerBroadcastChannel } from './broadcast-channel/ChargingStationWorkerBroadcastChannel'; +import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils'; +import { ChargingStationUtils } from './ChargingStationUtils'; +import { IdTagsCache } from './IdTagsCache'; import { - AutomaticTransactionGenerator, - ChargingStationConfigurationUtils, - ChargingStationUtils, - ChargingStationWorkerBroadcastChannel, - IdTagsCache, - MessageChannelUtils, - SharedLRUCache, -} from './internal'; -import { - // OCPP16IncomingRequestService, + OCPP16IncomingRequestService, OCPP16RequestService, - // OCPP16ResponseService, + OCPP16ResponseService, OCPP16ServiceUtils, OCPP20IncomingRequestService, OCPP20RequestService, - // OCPP20ResponseService, + OCPP20ResponseService, type OCPPIncomingRequestService, type OCPPRequestService, - // OCPPServiceUtils, + OCPPServiceUtils, } from './ocpp'; -import { OCPP16IncomingRequestService } from './ocpp/1.6/OCPP16IncomingRequestService'; -import { OCPP16ResponseService } from './ocpp/1.6/OCPP16ResponseService'; -import { OCPP20ResponseService } from './ocpp/2.0/OCPP20ResponseService'; -import { OCPPServiceUtils } from './ocpp/OCPPServiceUtils'; +import { SharedLRUCache } from './SharedLRUCache'; import { BaseError, OCPPError } from '../exception'; import { PerformanceStatistics } from '../performance'; import { @@ -42,7 +35,6 @@ import { type BootNotificationRequest, type BootNotificationResponse, type CachedRequest, - type ChargingStationAutomaticTransactionGeneratorConfiguration, type ChargingStationConfiguration, type ChargingStationInfo, type ChargingStationOcppConfiguration, @@ -75,8 +67,12 @@ import { PowerUnits, RegistrationStatusEnumType, RequestCommand, + type Reservation, + ReservationFilterKey, + ReservationTerminationReason, type Response, StandardParametersKey, + type Status, type StatusNotificationRequest, type StatusNotificationResponse, StopTransactionReason, @@ -96,9 +92,16 @@ import { Configuration, Constants, DCElectricUtils, - FileUtils, Utils, + buildChargingStationAutomaticTransactionGeneratorConfiguration, + buildConnectorsStatus, + buildEvsesStatus, + buildStartedMessage, + buildStoppedMessage, + buildUpdatedMessage, + handleFileException, logger, + watchJsonFile, } from '../utils'; export class ChargingStation { @@ -135,6 +138,7 @@ export class ChargingStation { private readonly sharedLRUCache: SharedLRUCache; private webSocketPingSetInterval!: NodeJS.Timeout; private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel; + private reservationExpirationSetInterval?: NodeJS.Timeout; constructor(index: number, templateFile: string) { this.started = false; @@ -163,11 +167,17 @@ export class ChargingStation { return new URL( `${ this.getSupervisionUrlOcppConfiguration() && - Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) + Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) && + Utils.isNotEmptyString( + ChargingStationConfigurationUtils.getConfigurationKey( + this, + this.getSupervisionUrlOcppKey() + )?.value + ) ? ChargingStationConfigurationUtils.getConfigurationKey( this, this.getSupervisionUrlOcppKey() - )?.value + ).value : this.configuredSupervisionUrl.href }/${this.stationInfo.chargingStationId}` ); @@ -185,8 +195,9 @@ export class ChargingStation { }; public hasIdTags(): boolean { - const idTagsFile = ChargingStationUtils.getIdTagsFile(this.stationInfo); - return Utils.isNotEmptyArray(this.idTagsCache.getIdTags(idTagsFile)); + return Utils.isNotEmptyArray( + this.idTagsCache.getIdTags(ChargingStationUtils.getIdTagsFile(this.stationInfo)) + ); } public getEnableStatistics(): boolean { @@ -221,26 +232,26 @@ export class ChargingStation { return this?.bootNotificationResponse?.status; } - public isInUnknownState(): boolean { + public inUnknownState(): boolean { return Utils.isNullOrUndefined(this?.bootNotificationResponse?.status); } - public isInPendingState(): boolean { + public inPendingState(): boolean { return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING; } - public isInAcceptedState(): boolean { + public inAcceptedState(): boolean { return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED; } - public isInRejectedState(): boolean { + public inRejectedState(): boolean { return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED; } public isRegistered(): boolean { return ( - this.isInUnknownState() === false && - (this.isInAcceptedState() === true || this.isInPendingState() === true) + this.inUnknownState() === false && + (this.inAcceptedState() === true || this.inPendingState() === true) ); } @@ -544,7 +555,8 @@ export class ChargingStation { ); } else { logger.error( - `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat` + `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, + not starting the heartbeat` ); } } @@ -572,13 +584,15 @@ export class ChargingStation { } if (!this.getConnectorStatus(connectorId)) { logger.error( - `${this.logPrefix()} Trying to start MeterValues on non existing connector id ${connectorId.toString()}` + `${this.logPrefix()} Trying to start MeterValues on non existing connector id + ${connectorId.toString()}` ); return; } if (this.getConnectorStatus(connectorId)?.transactionStarted === false) { logger.error( - `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction started` + `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} + with no transaction started` ); return; } else if ( @@ -586,7 +600,8 @@ export class ChargingStation { Utils.isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId) ) { logger.error( - `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction id` + `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} + with no transaction id` ); return; } @@ -638,9 +653,12 @@ export class ChargingStation { if (this.getEnableStatistics() === true) { this.performanceStatistics?.start(); } + if (this.hasFeatureProfile(SupportedFeatureProfiles.Reservation)) { + this.startReservationExpirationSetInterval(); + } this.openWSConnection(); // Monitor charging station template file - this.templateFileWatcher = FileUtils.watchJsonFile( + this.templateFileWatcher = watchJsonFile( this.templateFile, FileType.ChargingStationTemplate, this.logPrefix(), @@ -677,7 +695,7 @@ export class ChargingStation { } ); this.started = true; - parentPort?.postMessage(MessageChannelUtils.buildStartedMessage(this)); + parentPort?.postMessage(buildStartedMessage(this)); this.starting = false; } else { logger.warn(`${this.logPrefix()} Charging station is already starting...`); @@ -702,7 +720,7 @@ export class ChargingStation { delete this.bootNotificationResponse; this.started = false; this.saveConfiguration(); - parentPort?.postMessage(MessageChannelUtils.buildStoppedMessage(this)); + parentPort?.postMessage(buildStoppedMessage(this)); this.stopping = false; } else { logger.warn(`${this.logPrefix()} Charging station is already stopping...`); @@ -743,11 +761,12 @@ export class ChargingStation { terminateOpened: false, } ): void { - options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000; + options = { handshakeTimeout: this.getConnectionTimeout() * 1000, ...options }; params = { ...{ closeOpened: false, terminateOpened: false }, ...params }; if (this.started === false && this.starting === false) { logger.warn( - `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()} on stopped charging station` + `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()} + on stopped charging station` ); return; } @@ -766,7 +785,8 @@ export class ChargingStation { if (this.isWebSocketConnectionOpened() === true) { logger.warn( - `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} is already opened` + `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} + is already opened` ); return; } @@ -814,12 +834,29 @@ export class ChargingStation { public getAutomaticTransactionGeneratorConfiguration(): | AutomaticTransactionGeneratorConfiguration | undefined { + let automaticTransactionGeneratorConfiguration: + | AutomaticTransactionGeneratorConfiguration + | undefined; const automaticTransactionGeneratorConfigurationFromFile = this.getConfigurationFromFile()?.automaticTransactionGenerator; - if (automaticTransactionGeneratorConfigurationFromFile) { - return automaticTransactionGeneratorConfigurationFromFile; + if ( + this.getAutomaticTransactionGeneratorPersistentConfiguration() && + automaticTransactionGeneratorConfigurationFromFile + ) { + automaticTransactionGeneratorConfiguration = + automaticTransactionGeneratorConfigurationFromFile; + } else { + automaticTransactionGeneratorConfiguration = + this.getTemplateFromFile()?.AutomaticTransactionGenerator; } - return this.getTemplateFromFile()?.AutomaticTransactionGenerator; + return { + ...Constants.DEFAULT_ATG_CONFIGURATION, + ...automaticTransactionGeneratorConfiguration, + }; + } + + public getAutomaticTransactionGeneratorStatuses(): Status[] | undefined { + return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses; } public startAutomaticTransactionGenerator(connectorIds?: number[]): void { @@ -831,8 +868,8 @@ export class ChargingStation { } else { this.automaticTransactionGenerator?.start(); } - this.saveChargingStationAutomaticTransactionGeneratorConfiguration(); - parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this)); + this.saveAutomaticTransactionGeneratorConfiguration(); + parentPort?.postMessage(buildUpdatedMessage(this)); } public stopAutomaticTransactionGenerator(connectorIds?: number[]): void { @@ -843,8 +880,8 @@ export class ChargingStation { } else { this.automaticTransactionGenerator?.stop(); } - this.saveChargingStationAutomaticTransactionGeneratorConfiguration(); - parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this)); + this.saveAutomaticTransactionGeneratorConfiguration(); + parentPort?.postMessage(buildUpdatedMessage(this)); } public async stopTransactionOnConnector( @@ -884,6 +921,186 @@ export class ChargingStation { ); } + public getReservationOnConnectorId0Enabled(): boolean { + return Utils.convertToBoolean( + ChargingStationConfigurationUtils.getConfigurationKey( + this, + StandardParametersKey.ReserveConnectorZeroSupported + ).value + ); + } + + public async addReservation(reservation: Reservation): Promise { + const [exists, reservationFound] = this.doesReservationExists(reservation); + if (exists) { + await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING); + } + this.getConnectorStatus(reservation.connectorId).reservation = reservation; + await OCPPServiceUtils.sendAndSetConnectorStatus( + this, + reservation.connectorId, + ConnectorStatusEnum.Reserved, + null, + { send: reservation.connectorId !== 0 } + ); + } + + public async removeReservation( + reservation: Reservation, + reason?: ReservationTerminationReason + ): Promise { + const connector = this.getConnectorStatus(reservation.connectorId); + switch (reason) { + case ReservationTerminationReason.CONNECTOR_STATE_CHANGED: + delete connector.reservation; + break; + case ReservationTerminationReason.TRANSACTION_STARTED: + delete connector.reservation; + break; + case ReservationTerminationReason.RESERVATION_CANCELED || + ReservationTerminationReason.REPLACE_EXISTING || + ReservationTerminationReason.EXPIRED: + await OCPPServiceUtils.sendAndSetConnectorStatus( + this, + reservation.connectorId, + ConnectorStatusEnum.Available, + null, + { send: reservation.connectorId !== 0 } + ); + delete connector.reservation; + break; + default: + break; + } + } + + public getReservationBy(filterKey: ReservationFilterKey, value: number | string): Reservation { + if (this.hasEvses) { + for (const evse of this.evses.values()) { + for (const connector of evse.connectors.values()) { + if (connector?.reservation?.[filterKey] === value) { + return connector.reservation; + } + } + } + } else { + for (const connector of this.connectors.values()) { + if (connector?.reservation?.[filterKey] === value) { + return connector.reservation; + } + } + } + } + + public doesReservationExists(reservation: Partial): [boolean, Reservation] { + const foundReservation = this.getReservationBy( + ReservationFilterKey.RESERVATION_ID, + reservation?.id + ); + return Utils.isUndefined(foundReservation) ? [false, null] : [true, foundReservation]; + } + + public startReservationExpirationSetInterval(customInterval?: number): void { + const interval = + customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_OBSERVATION_INTERVAL; + logger.info( + `${this.logPrefix()} Reservation expiration date interval is set to ${interval} + and starts on charging station now` + ); + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.reservationExpirationSetInterval = setInterval(async (): Promise => { + if (this.hasEvses) { + for (const evse of this.evses.values()) { + for (const connector of evse.connectors.values()) { + if (connector?.reservation?.expiryDate.toString() < new Date().toISOString()) { + await this.removeReservation( + connector.reservation, + ReservationTerminationReason.EXPIRED + ); + } + } + } + } else { + for (const connector of this.connectors.values()) { + if (connector?.reservation?.expiryDate.toString() < new Date().toISOString()) { + await this.removeReservation( + connector.reservation, + ReservationTerminationReason.EXPIRED + ); + } + } + } + }, interval); + } + + public restartReservationExpiryDateSetInterval(): void { + this.stopReservationExpirationSetInterval(); + this.startReservationExpirationSetInterval(); + } + + public validateIncomingRequestWithReservation(connectorId: number, idTag: string): boolean { + const reservation = this.getReservationBy(ReservationFilterKey.CONNECTOR_ID, connectorId); + return !Utils.isUndefined(reservation) && reservation.idTag === idTag; + } + + public isConnectorReservable( + reservationId: number, + idTag?: string, + connectorId?: number + ): boolean { + const [alreadyExists] = this.doesReservationExists({ id: reservationId }); + if (alreadyExists) { + return alreadyExists; + } + const userReservedAlready = Utils.isUndefined( + this.getReservationBy(ReservationFilterKey.ID_TAG, idTag) + ) + ? false + : true; + const notConnectorZero = Utils.isUndefined(connectorId) ? true : connectorId > 0; + const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0; + return !alreadyExists && !userReservedAlready && notConnectorZero && freeConnectorsAvailable; + } + + private getNumberOfReservableConnectors(): number { + let reservableConnectors = 0; + if (this.hasEvses) { + for (const evse of this.evses.values()) { + reservableConnectors = this.countReservableConnectors(evse.connectors); + } + } else { + reservableConnectors = this.countReservableConnectors(this.connectors); + } + return reservableConnectors - this.getNumberOfReservationsOnConnectorZero(); + } + + private countReservableConnectors(connectors: Map) { + let reservableConnectors = 0; + for (const [connectorId, connector] of connectors) { + if (connectorId === 0) { + continue; + } + if (connector.status === ConnectorStatusEnum.Available) { + ++reservableConnectors; + } + } + return reservableConnectors; + } + + private getNumberOfReservationsOnConnectorZero(): number { + let numberOfReservations = 0; + if (this.hasEvses) { + for (const evse of this.evses.values()) { + if (evse.connectors.get(0)?.reservation) { + ++numberOfReservations; + } + } + } else if (this.connectors.get(0)?.reservation) { + ++numberOfReservations; + } + return numberOfReservations; + } + private flushMessageBuffer(): void { if (this.messageBuffer.size > 0) { for (const message of this.messageBuffer.values()) { @@ -911,6 +1128,12 @@ export class ChargingStation { return this.stationInfo.supervisionUrlOcppConfiguration ?? false; } + private stopReservationExpirationSetInterval(): void { + if (this.reservationExpirationSetInterval) { + clearInterval(this.reservationExpirationSetInterval); + } + } + private getSupervisionUrlOcppKey(): string { return this.stationInfo.supervisionUrlOcppKey ?? VendorParametersKey.ConnectionUrl; } @@ -935,7 +1158,7 @@ export class ChargingStation { this.templateFileHash = template.templateHash; } } catch (error) { - FileUtils.handleFileException( + handleFileException( this.templateFile, FileType.ChargingStationTemplate, error as NodeJS.ErrnoException, @@ -947,7 +1170,7 @@ export class ChargingStation { private getStationInfoFromTemplate(): ChargingStationInfo { const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile(); - ChargingStationUtils.checkTemplateFile(stationTemplate, this.logPrefix(), this.templateFile); + ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); ChargingStationUtils.warnTemplateKeysDeprecation( stationTemplate, this.logPrefix(), @@ -1054,23 +1277,27 @@ export class ChargingStation { return this.stationInfo?.stationInfoPersistentConfiguration ?? true; } + private getAutomaticTransactionGeneratorPersistentConfiguration(): boolean { + return this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration ?? true; + } + private handleUnsupportedVersion(version: OCPPVersion) { - const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`; + const errorMsg = `Unsupported protocol version '${version}' configured + in template file ${this.templateFile}`; logger.error(`${this.logPrefix()} ${errorMsg}`); throw new BaseError(errorMsg); } private initialize(): void { const stationTemplate = this.getTemplateFromFile(); - ChargingStationUtils.checkTemplateFile(stationTemplate, this.logPrefix(), this.templateFile); + ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); this.configurationFile = path.join( path.dirname(this.templateFile.replace('station-templates', 'configurations')), `${ChargingStationUtils.getHashId(this.index, stationTemplate)}.json` ); const chargingStationConfiguration = this.getConfigurationFromFile(); - const featureFlag = false; if ( - featureFlag && + chargingStationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash && (chargingStationConfiguration?.connectorsStatus || chargingStationConfiguration?.evsesStatus) ) { this.initializeConnectorsOrEvsesFromFile(chargingStationConfiguration); @@ -1453,7 +1680,7 @@ export class ChargingStation { if (stationTemplate?.Evses) { const evsesConfigHash = crypto .createHash(Constants.DEFAULT_HASH_ALGORITHM) - .update(`${JSON.stringify(stationTemplate?.Evses)}`) + .update(JSON.stringify(stationTemplate?.Evses)) .digest('hex'); const evsesConfigChanged = this.evses?.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash; @@ -1497,7 +1724,7 @@ export class ChargingStation { private getConfigurationFromFile(): ChargingStationConfiguration | undefined { let configuration: ChargingStationConfiguration | undefined; - if (this.configurationFile && fs.existsSync(this.configurationFile)) { + if (Utils.isNotEmptyString(this.configurationFile) && fs.existsSync(this.configurationFile)) { try { if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) { configuration = this.sharedLRUCache.getChargingStationConfiguration( @@ -1514,7 +1741,7 @@ export class ChargingStation { this.configurationFileHash = configuration.configurationHash; } } catch (error) { - FileUtils.handleFileException( + handleFileException( this.configurationFile, FileType.ChargingStationConfiguration, error as NodeJS.ErrnoException, @@ -1525,18 +1752,10 @@ export class ChargingStation { return configuration; } - private saveChargingStationAutomaticTransactionGeneratorConfiguration( - stationTemplate?: ChargingStationTemplate - ): void { - this.saveConfiguration({ - automaticTransactionGenerator: (stationTemplate ?? this.getTemplateFromFile()) - .AutomaticTransactionGenerator, - ...(!Utils.isNullOrUndefined(this.automaticTransactionGenerator?.connectorsStatus) && { - automaticTransactionGeneratorStatuses: [ - ...this.automaticTransactionGenerator.connectorsStatus.values(), - ], - }), - }); + private saveAutomaticTransactionGeneratorConfiguration(): void { + if (this.getAutomaticTransactionGeneratorPersistentConfiguration()) { + this.saveConfiguration(); + } } private saveConnectorsStatus() { @@ -1547,10 +1766,8 @@ export class ChargingStation { this.saveConfiguration(); } - private saveConfiguration( - chargingStationAutomaticTransactionGeneratorConfiguration?: ChargingStationAutomaticTransactionGeneratorConfiguration - ): void { - if (this.configurationFile) { + private saveConfiguration(): void { + if (Utils.isNotEmptyString(this.configurationFile)) { try { if (!fs.existsSync(path.dirname(this.configurationFile))) { fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true }); @@ -1559,39 +1776,44 @@ export class ChargingStation { Utils.cloneObject(this.getConfigurationFromFile()) ?? {}; if (this.getStationInfoPersistentConfiguration() && this.stationInfo) { configurationData.stationInfo = this.stationInfo; + } else { + delete configurationData.stationInfo; } if (this.getOcppPersistentConfiguration() && this.ocppConfiguration?.configurationKey) { configurationData.configurationKey = this.ocppConfiguration.configurationKey; + } else { + delete configurationData.configurationKey; } - if (chargingStationAutomaticTransactionGeneratorConfiguration) { - configurationData = merge( - configurationData, - chargingStationAutomaticTransactionGeneratorConfiguration - ); + configurationData = merge( + configurationData, + buildChargingStationAutomaticTransactionGeneratorConfiguration(this) + ); + if ( + !this.getAutomaticTransactionGeneratorPersistentConfiguration() || + !this.getAutomaticTransactionGeneratorConfiguration() + ) { + delete configurationData.automaticTransactionGenerator; } if (this.connectors.size > 0) { - configurationData.connectorsStatus = [...this.connectors.values()].map( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest - ); + configurationData.connectorsStatus = buildConnectorsStatus(this); + } else { + delete configurationData.connectorsStatus; } if (this.evses.size > 0) { - configurationData.evsesStatus = [...this.evses.values()].map((evseStatus) => { - const status = { - ...evseStatus, - connectorsStatus: [...evseStatus.connectors.values()].map( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest - ), - }; - delete status.connectors; - return status as EvseStatusConfiguration; - }); + configurationData.evsesStatus = buildEvsesStatus(this); + } else { + delete configurationData.evsesStatus; } delete configurationData.configurationHash; const configurationHash = crypto .createHash(Constants.DEFAULT_HASH_ALGORITHM) - .update(JSON.stringify(configurationData)) + .update( + JSON.stringify({ + stationInfo: configurationData.stationInfo, + configurationKey: configurationData.configurationKey, + automaticTransactionGenerator: configurationData.automaticTransactionGenerator, + } as ChargingStationConfiguration) + ) .digest('hex'); if (this.configurationFileHash !== configurationHash) { AsyncLock.acquire(AsyncLockType.configuration) @@ -1608,7 +1830,7 @@ export class ChargingStation { this.configurationFileHash = configurationHash; }) .catch((error) => { - FileUtils.handleFileException( + handleFileException( this.configurationFile, FileType.ChargingStationConfiguration, error as NodeJS.ErrnoException, @@ -1626,7 +1848,7 @@ export class ChargingStation { ); } } catch (error) { - FileUtils.handleFileException( + handleFileException( this.configurationFile, FileType.ChargingStationConfiguration, error as NodeJS.ErrnoException, @@ -1645,9 +1867,11 @@ export class ChargingStation { } private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined { - if (this.getOcppPersistentConfiguration() === true) { - return { configurationKey: this.getConfigurationFromFile()?.configurationKey }; + const configurationKey = this.getConfigurationFromFile()?.configurationKey; + if (this.getOcppPersistentConfiguration() === true && configurationKey) { + return { configurationKey }; } + return undefined; } private getOcppConfiguration(): ChargingStationOcppConfiguration | undefined { @@ -1675,11 +1899,11 @@ export class ChargingStation { skipBufferingOnError: true, }); if (this.isRegistered() === false) { - this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++; + this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount; await Utils.sleep( this?.bootNotificationResponse?.interval ? this.bootNotificationResponse.interval * 1000 - : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL + : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL ); } } while ( @@ -1689,7 +1913,7 @@ export class ChargingStation { ); } if (this.isRegistered() === true) { - if (this.isInAcceptedState() === true) { + if (this.inAcceptedState() === true) { await this.startMessageSequence(); } } else { @@ -1699,7 +1923,7 @@ export class ChargingStation { } this.wsConnectionRestarted = false; this.autoReconnectRetryCount = 0; - parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this)); + parentPort?.postMessage(buildUpdatedMessage(this)); } else { logger.warn( `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed` @@ -1729,7 +1953,7 @@ export class ChargingStation { this.started === true && (await this.reconnect()); break; } - parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this)); + parentPort?.postMessage(buildUpdatedMessage(this)); } private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined { @@ -1839,7 +2063,7 @@ export class ChargingStation { logger.error(`${this.logPrefix()} ${errorMsg}`); throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg); } - parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this)); + parentPort?.postMessage(buildUpdatedMessage(this)); } else { throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming message is not an array', null, { request, @@ -2172,6 +2396,7 @@ export class ChargingStation { } private getConfiguredSupervisionUrl(): URL { + let configuredSupervisionUrl: string; const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls(); if (Utils.isNotEmptyArray(supervisionUrls)) { let configuredSupervisionUrlIndex: number; @@ -2193,9 +2418,16 @@ export class ChargingStation { configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length; break; } - return new URL(supervisionUrls[configuredSupervisionUrlIndex]); + configuredSupervisionUrl = supervisionUrls[configuredSupervisionUrlIndex]; + } else { + configuredSupervisionUrl = supervisionUrls as string; + } + if (Utils.isNotEmptyString(configuredSupervisionUrl)) { + return new URL(configuredSupervisionUrl); } - return new URL(supervisionUrls as string); + const errorMsg = 'No supervision url(s) configured'; + logger.error(`${this.logPrefix()} ${errorMsg}`); + throw new BaseError(`${errorMsg}`); } private stopHeartbeat(): void { @@ -2229,7 +2461,7 @@ export class ChargingStation { this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries() || this.getAutoReconnectMaxRetries() === -1 ) { - this.autoReconnectRetryCount++; + ++this.autoReconnectRetryCount; const reconnectDelay = this.getReconnectExponentialDelay() ? Utils.exponentialDelay(this.autoReconnectRetryCount) : this.getConnectionTimeout() * 1000;