X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=1494cc56365d983ec779c50dbd858af18cad739f;hb=b2b606263e2676354259164d532ff9aa91ccdf87;hp=a3f6769607c5b01bb9774bf614a71f31a80a5afe;hpb=6e3d9d04815abfe85d31735fc38bdf0253d85026;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index a3f67696..1494cc56 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -1,18 +1,47 @@ // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. -import crypto from 'node:crypto'; -import fs from 'node:fs'; -import path from 'node:path'; +import { createHash } from 'node:crypto'; +import { + type FSWatcher, + closeSync, + existsSync, + mkdirSync, + openSync, + readFileSync, + writeFileSync, +} from 'node:fs'; +import { dirname, join } from 'node:path'; import { URL } from 'node:url'; import { parentPort } from 'node:worker_threads'; import merge from 'just-merge'; -import WebSocket, { type RawData } from 'ws'; +import { type RawData, WebSocket } from 'ws'; import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator'; import { ChargingStationWorkerBroadcastChannel } from './broadcast-channel/ChargingStationWorkerBroadcastChannel'; import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils'; -import { ChargingStationUtils } from './ChargingStationUtils'; +import { + buildConnectorsMap, + checkConnectorsConfiguration, + checkStationInfoConnectorStatus, + checkTemplate, + countReservableConnectors, + createBootNotificationRequest, + createSerialNumber, + getAmperageLimitationUnitDivider, + getBootConnectorStatus, + getChargingStationConnectorChargingProfilesPowerLimit, + getChargingStationId, + getDefaultVoltageOut, + getHashId, + getIdTagsFile, + getMaxNumberOfEvses, + getPhaseRotationValue, + initializeConnectorsMapStatus, + propagateSerialNumber, + stationTemplateToStationInfo, + warnTemplateKeysDeprecation, +} from './ChargingStationUtils'; import { IdTagsCache } from './IdTagsCache'; import { OCPP16IncomingRequestService, @@ -92,15 +121,30 @@ import { Configuration, Constants, DCElectricUtils, - Utils, buildChargingStationAutomaticTransactionGeneratorConfiguration, buildConnectorsStatus, buildEvsesStatus, buildStartedMessage, buildStoppedMessage, buildUpdatedMessage, + cloneObject, + convertToBoolean, + convertToInt, + exponentialDelay, + formatDurationMilliSeconds, + formatDurationSeconds, + getRandomInteger, + getWebSocketCloseEventStatusString, handleFileException, + isNotEmptyArray, + isNotEmptyString, + isNullOrUndefined, + isUndefined, + logPrefix, logger, + roundTo, + secureRandom, + sleep, watchJsonFile, } from '../utils'; @@ -133,12 +177,12 @@ export class ChargingStation { private configuredSupervisionUrl!: URL; private wsConnectionRestarted: boolean; private autoReconnectRetryCount: number; - private templateFileWatcher!: fs.FSWatcher | undefined; + private templateFileWatcher!: FSWatcher | undefined; private templateFileHash!: string; private readonly sharedLRUCache: SharedLRUCache; private webSocketPingSetInterval!: NodeJS.Timeout; private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel; - private reservationExpiryDateSetInterval?: NodeJS.Timeout; + private reservationExpirationSetInterval?: NodeJS.Timeout; constructor(index: number, templateFile: string) { this.started = false; @@ -167,8 +211,8 @@ export class ChargingStation { return new URL( `${ this.getSupervisionUrlOcppConfiguration() && - Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) && - Utils.isNotEmptyString( + isNotEmptyString(this.getSupervisionUrlOcppKey()) && + isNotEmptyString( ChargingStationConfigurationUtils.getConfigurationKey( this, this.getSupervisionUrlOcppKey() @@ -184,20 +228,18 @@ export class ChargingStation { } public logPrefix = (): string => { - return Utils.logPrefix( + return logPrefix( ` ${ - (Utils.isNotEmptyString(this?.stationInfo?.chargingStationId) + (isNotEmptyString(this?.stationInfo?.chargingStationId) ? this?.stationInfo?.chargingStationId - : ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile())) ?? + : getChargingStationId(this.index, this.getTemplateFromFile())) ?? 'Error at building log prefix' } |` ); }; public hasIdTags(): boolean { - return Utils.isNotEmptyArray( - this.idTagsCache.getIdTags(ChargingStationUtils.getIdTagsFile(this.stationInfo)) - ); + return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo))); } public getEnableStatistics(): boolean { @@ -208,17 +250,11 @@ export class ChargingStation { return this.stationInfo.mustAuthorizeAtRemoteStart ?? true; } - public getPayloadSchemaValidation(): boolean { - return this.stationInfo.payloadSchemaValidation ?? true; - } - public getNumberOfPhases(stationInfo?: ChargingStationInfo): number | undefined { const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo; switch (this.getCurrentOutType(stationInfo)) { case CurrentType.AC: - return !Utils.isUndefined(localStationInfo.numberOfPhases) - ? localStationInfo.numberOfPhases - : 3; + return !isUndefined(localStationInfo.numberOfPhases) ? localStationInfo.numberOfPhases : 3; case CurrentType.DC: return 0; } @@ -233,7 +269,7 @@ export class ChargingStation { } public inUnknownState(): boolean { - return Utils.isNullOrUndefined(this?.bootNotificationResponse?.status); + return isNullOrUndefined(this?.bootNotificationResponse?.status); } public inPendingState(): boolean { @@ -280,9 +316,6 @@ export class ChargingStation { public getNumberOfConnectors(): number { if (this.hasEvses) { - if (this.evses.size === 0) { - throw new BaseError('Evses not initialized, cannot get number of connectors'); - } let numberOfConnectors = 0; for (const [evseId, evseStatus] of this.evses) { if (evseId > 0) { @@ -291,16 +324,10 @@ export class ChargingStation { } return numberOfConnectors; } - if (this.connectors.size === 0) { - throw new BaseError('Connectors not initialized, cannot get number of connectors'); - } return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size; } public getNumberOfEvses(): number { - if (this.evses.size === 0) { - throw new BaseError('Evses not initialized, cannot get number of evses'); - } return this.evses.has(0) ? this.evses.size - 1 : this.evses.size; } @@ -321,11 +348,11 @@ export class ChargingStation { } public getOcppStrictCompliance(): boolean { - return this.stationInfo?.ocppStrictCompliance ?? false; + return this.stationInfo?.ocppStrictCompliance ?? true; } public getVoltageOut(stationInfo?: ChargingStationInfo): number | undefined { - const defaultVoltageOut = ChargingStationUtils.getDefaultVoltageOut( + const defaultVoltageOut = getDefaultVoltageOut( this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile @@ -341,7 +368,7 @@ export class ChargingStation { public getConnectorMaximumAvailablePower(connectorId: number): number { let connectorAmperageLimitationPowerLimit: number; if ( - !Utils.isNullOrUndefined(this.getAmperageLimitation()) && + !isNullOrUndefined(this.getAmperageLimitation()) && this.getAmperageLimitation() < this.stationInfo?.maximumAmperage ) { connectorAmperageLimitationPowerLimit = @@ -357,7 +384,7 @@ export class ChargingStation { } const connectorMaximumPower = this.getMaximumPower() / this.powerDivider; const connectorChargingProfilesPowerLimit = - ChargingStationUtils.getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId); + getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId); return Math.min( isNaN(connectorMaximumPower) ? Infinity : connectorMaximumPower, isNaN(connectorAmperageLimitationPowerLimit) @@ -378,10 +405,7 @@ export class ChargingStation { } } else { for (const connectorId of this.connectors.keys()) { - if ( - connectorId > 0 && - this.getConnectorStatus(connectorId)?.transactionId === transactionId - ) { + if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) { return this.getConnectorStatus(connectorId)?.transactionIdTag; } } @@ -391,7 +415,10 @@ export class ChargingStation { public getNumberOfRunningTransactions(): number { let trxCount = 0; if (this.hasEvses) { - for (const evseStatus of this.evses.values()) { + for (const [evseId, evseStatus] of this.evses) { + if (evseId === 0) { + continue; + } for (const connectorStatus of evseStatus.connectors.values()) { if (connectorStatus.transactionStarted === true) { ++trxCount; @@ -447,10 +474,7 @@ export class ChargingStation { } } else { for (const connectorId of this.connectors.keys()) { - if ( - connectorId > 0 && - this.getConnectorStatus(connectorId)?.transactionId === transactionId - ) { + if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) { return connectorId; } } @@ -476,9 +500,7 @@ export class ChargingStation { this, StandardParametersKey.AuthorizeRemoteTxRequests ); - return authorizeRemoteTxRequests - ? Utils.convertToBoolean(authorizeRemoteTxRequests.value) - : false; + return authorizeRemoteTxRequests ? convertToBoolean(authorizeRemoteTxRequests.value) : false; } public getLocalAuthListEnabled(): boolean { @@ -486,7 +508,7 @@ export class ChargingStation { this, StandardParametersKey.LocalAuthListEnabled ); - return localAuthListEnabled ? Utils.convertToBoolean(localAuthListEnabled.value) : false; + return localAuthListEnabled ? convertToBoolean(localAuthListEnabled.value) : false; } public getHeartbeatInterval(): number { @@ -495,14 +517,14 @@ export class ChargingStation { StandardParametersKey.HeartbeatInterval ); if (HeartbeatInterval) { - return Utils.convertToInt(HeartbeatInterval.value) * 1000; + return convertToInt(HeartbeatInterval.value) * 1000; } const HeartBeatInterval = ChargingStationConfigurationUtils.getConfigurationKey( this, StandardParametersKey.HeartBeatInterval ); if (HeartBeatInterval) { - return Utils.convertToInt(HeartBeatInterval.value) * 1000; + return convertToInt(HeartBeatInterval.value) * 1000; } this.stationInfo?.autoRegister === false && logger.warn( @@ -516,7 +538,7 @@ export class ChargingStation { public setSupervisionUrl(url: string): void { if ( this.getSupervisionUrlOcppConfiguration() && - Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) + isNotEmptyString(this.getSupervisionUrlOcppKey()) ) { ChargingStationConfigurationUtils.setConfigurationKeyValue( this, @@ -543,13 +565,13 @@ export class ChargingStation { }); }, this.getHeartbeatInterval()); logger.info( - `${this.logPrefix()} Heartbeat started every ${Utils.formatDurationMilliSeconds( + `${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds( this.getHeartbeatInterval() )}` ); } else if (this.heartbeatSetInterval) { logger.info( - `${this.logPrefix()} Heartbeat already started every ${Utils.formatDurationMilliSeconds( + `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds( this.getHeartbeatInterval() )}` ); @@ -597,7 +619,7 @@ export class ChargingStation { return; } else if ( this.getConnectorStatus(connectorId)?.transactionStarted === true && - Utils.isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId) + isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId) ) { logger.error( `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} @@ -654,7 +676,7 @@ export class ChargingStation { this.performanceStatistics?.start(); } if (this.hasFeatureProfile(SupportedFeatureProfiles.Reservation)) { - this.startReservationExpiryDateSetInterval(); + this.startReservationExpirationSetInterval(); } this.openWSConnection(); // Monitor charging station template file @@ -664,7 +686,7 @@ export class ChargingStation { this.logPrefix(), undefined, (event, filename): void => { - if (Utils.isNotEmptyString(filename) && event === 'change') { + if (isNotEmptyString(filename) && event === 'change') { try { logger.debug( `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${ @@ -674,6 +696,7 @@ export class ChargingStation { this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash); // Initialize this.initialize(); + this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo)); // Restart the ATG this.stopAutomaticTransactionGenerator(); if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) { @@ -732,7 +755,7 @@ export class ChargingStation { public async reset(reason?: StopTransactionReason): Promise { await this.stop(reason); - await Utils.sleep(this.stationInfo.resetTime); + await sleep(this.stationInfo.resetTime); this.initialize(); this.start(); } @@ -771,8 +794,8 @@ export class ChargingStation { return; } if ( - !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) && - !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword) + !isNullOrUndefined(this.stationInfo.supervisionUser) && + !isNullOrUndefined(this.stationInfo.supervisionPassword) ) { options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`; } @@ -861,7 +884,7 @@ export class ChargingStation { public startAutomaticTransactionGenerator(connectorIds?: number[]): void { this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this); - if (Utils.isNotEmptyArray(connectorIds)) { + if (isNotEmptyArray(connectorIds)) { for (const connectorId of connectorIds) { this.automaticTransactionGenerator?.startConnector(connectorId); } @@ -873,7 +896,7 @@ export class ChargingStation { } public stopAutomaticTransactionGenerator(connectorIds?: number[]): void { - if (Utils.isNotEmptyArray(connectorIds)) { + if (isNotEmptyArray(connectorIds)) { for (const connectorId of connectorIds) { this.automaticTransactionGenerator?.stopConnector(connectorId); } @@ -922,7 +945,7 @@ export class ChargingStation { } public getReservationOnConnectorId0Enabled(): boolean { - return Utils.convertToBoolean( + return convertToBoolean( ChargingStationConfigurationUtils.getConfigurationKey( this, StandardParametersKey.ReserveConnectorZeroSupported @@ -933,25 +956,15 @@ export class ChargingStation { public async addReservation(reservation: Reservation): Promise { const [exists, reservationFound] = this.doesReservationExists(reservation); if (exists) { - await this.removeReservation(reservationFound); + await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING); } - const connectorStatus = this.getConnectorStatus(reservation.connectorId); - connectorStatus.reservation = reservation; - connectorStatus.status = ConnectorStatusEnum.Reserved; - if (reservation.connectorId === 0) { - return; - } - await this.ocppRequestService.requestHandler< - StatusNotificationRequest, - StatusNotificationResponse - >( + this.getConnectorStatus(reservation.connectorId).reservation = reservation; + await OCPPServiceUtils.sendAndSetConnectorStatus( this, - RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( - this, - reservation.connectorId, - ConnectorStatusEnum.Reserved - ) + reservation.connectorId, + ConnectorStatusEnum.Reserved, + null, + { send: reservation.connectorId !== 0 } ); } @@ -961,51 +974,45 @@ export class ChargingStation { ): Promise { const connector = this.getConnectorStatus(reservation.connectorId); switch (reason) { - case ReservationTerminationReason.TRANSACTION_STARTED: { + case ReservationTerminationReason.CONNECTOR_STATE_CHANGED: delete connector.reservation; - if (reservation.connectorId === 0) { - connector.status = ConnectorStatusEnum.Available; - } break; - } - case ReservationTerminationReason.CONNECTOR_STATE_CHANGED: { + case ReservationTerminationReason.TRANSACTION_STARTED: delete connector.reservation; break; - } - default: { - // ReservationTerminationReason.EXPIRED, ReservationTerminationReason.CANCELED - connector.status = ConnectorStatusEnum.Available; - delete connector.reservation; - await this.ocppRequestService.requestHandler< - StatusNotificationRequest, - StatusNotificationResponse - >( + case ReservationTerminationReason.RESERVATION_CANCELED: + case ReservationTerminationReason.REPLACE_EXISTING: + case ReservationTerminationReason.EXPIRED: + await OCPPServiceUtils.sendAndSetConnectorStatus( this, - RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( - this, - reservation.connectorId, - ConnectorStatusEnum.Available - ) + reservation.connectorId, + ConnectorStatusEnum.Available, + null, + { send: reservation.connectorId !== 0 } ); + delete connector.reservation; + break; + default: break; - } } } - public getReservationBy(key: string, value: number | string): Reservation { + public getReservationBy( + filterKey: ReservationFilterKey, + value: number | string + ): Reservation | undefined { if (this.hasEvses) { - for (const evse of this.evses.values()) { - for (const connector of evse.connectors.values()) { - if (connector?.reservation?.[key] === value) { - return connector.reservation; + for (const evseStatus of this.evses.values()) { + for (const connectorStatus of evseStatus.connectors.values()) { + if (connectorStatus?.reservation?.[filterKey] === value) { + return connectorStatus.reservation; } } } } else { - for (const connector of this.connectors.values()) { - if (connector?.reservation?.[key] === value) { - return connector.reservation; + for (const connectorStatus of this.connectors.values()) { + if (connectorStatus?.reservation?.[filterKey] === value) { + return connectorStatus.reservation; } } } @@ -1016,30 +1023,37 @@ export class ChargingStation { ReservationFilterKey.RESERVATION_ID, reservation?.id ); - return Utils.isUndefined(foundReservation) ? [false, null] : [true, foundReservation]; + return isUndefined(foundReservation) ? [false, null] : [true, foundReservation]; } - public startReservationExpiryDateSetInterval(customInterval?: number): void { + 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 CS now` + and starts on charging station now` ); // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.reservationExpiryDateSetInterval = setInterval(async (): Promise => { + this.reservationExpirationSetInterval = setInterval(async (): Promise => { + const now = new Date(); 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); + for (const evseStatus of this.evses.values()) { + for (const connectorStatus of evseStatus.connectors.values()) { + if (connectorStatus?.reservation?.expiryDate < now) { + await this.removeReservation( + connectorStatus.reservation, + ReservationTerminationReason.EXPIRED + ); } } } } else { - for (const connector of this.connectors.values()) { - if (connector?.reservation?.expiryDate.toString() < new Date().toISOString()) { - await this.removeReservation(connector.reservation); + for (const connectorStatus of this.connectors.values()) { + if (connectorStatus?.reservation?.expiryDate < now) { + await this.removeReservation( + connectorStatus.reservation, + ReservationTerminationReason.EXPIRED + ); } } } @@ -1047,13 +1061,12 @@ export class ChargingStation { } public restartReservationExpiryDateSetInterval(): void { - this.stopReservationExpiryDateSetInterval(); - this.startReservationExpiryDateSetInterval(); + 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; + return this.getReservationBy(ReservationFilterKey.CONNECTOR_ID, connectorId)?.idTag === idTag; } public isConnectorReservable( @@ -1065,12 +1078,12 @@ export class ChargingStation { if (alreadyExists) { return alreadyExists; } - const userReservedAlready = Utils.isUndefined( + const userReservedAlready = isUndefined( this.getReservationBy(ReservationFilterKey.ID_TAG, idTag) ) ? false : true; - const notConnectorZero = Utils.isUndefined(connectorId) ? true : connectorId > 0; + const notConnectorZero = isUndefined(connectorId) ? true : connectorId > 0; const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0; return !alreadyExists && !userReservedAlready && notConnectorZero && freeConnectorsAvailable; } @@ -1078,36 +1091,19 @@ export class ChargingStation { private getNumberOfReservableConnectors(): number { let reservableConnectors = 0; if (this.hasEvses) { - for (const evse of this.evses.values()) { - reservableConnectors = this.countReservableConnectors(evse.connectors); + for (const evseStatus of this.evses.values()) { + reservableConnectors += countReservableConnectors(evseStatus.connectors); } } else { - reservableConnectors = this.countReservableConnectors(this.connectors); + reservableConnectors = countReservableConnectors(this.connectors); } return reservableConnectors - this.getNumberOfReservationsOnConnectorZero(); } - private countReservableConnectors(connectors: Map) { - let reservableConnectors = 0; - for (const [id, connector] of connectors) { - if (id === 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; - } - } + if (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation) { + ++numberOfReservations; } else if (this.connectors.get(0)?.reservation) { ++numberOfReservations; } @@ -1141,9 +1137,9 @@ export class ChargingStation { return this.stationInfo.supervisionUrlOcppConfiguration ?? false; } - private stopReservationExpiryDateSetInterval(): void { - if (this.reservationExpiryDateSetInterval) { - clearInterval(this.reservationExpiryDateSetInterval); + private stopReservationExpirationSetInterval(): void { + if (this.reservationExpirationSetInterval) { + clearInterval(this.reservationExpirationSetInterval); } } @@ -1159,12 +1155,9 @@ export class ChargingStation { } else { const measureId = `${FileType.ChargingStationTemplate} read`; const beginId = PerformanceStatistics.beginMeasure(measureId); - template = JSON.parse( - fs.readFileSync(this.templateFile, 'utf8') - ) as ChargingStationTemplate; + template = JSON.parse(readFileSync(this.templateFile, 'utf8')) as ChargingStationTemplate; PerformanceStatistics.endMeasure(measureId, beginId); - template.templateHash = crypto - .createHash(Constants.DEFAULT_HASH_ALGORITHM) + template.templateHash = createHash(Constants.DEFAULT_HASH_ALGORITHM) .update(JSON.stringify(template)) .digest('hex'); this.sharedLRUCache.setChargingStationTemplate(template); @@ -1183,31 +1176,19 @@ export class ChargingStation { private getStationInfoFromTemplate(): ChargingStationInfo { const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile(); - ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); - ChargingStationUtils.warnTemplateKeysDeprecation( - stationTemplate, - this.logPrefix(), - this.templateFile - ); + checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); + warnTemplateKeysDeprecation(stationTemplate, this.logPrefix(), this.templateFile); if (stationTemplate?.Connectors) { - ChargingStationUtils.checkConnectorsConfiguration( - stationTemplate, - this.logPrefix(), - this.templateFile - ); + checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile); } - const stationInfo: ChargingStationInfo = - ChargingStationUtils.stationTemplateToStationInfo(stationTemplate); - stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate); - stationInfo.chargingStationId = ChargingStationUtils.getChargingStationId( - this.index, - stationTemplate - ); + const stationInfo: ChargingStationInfo = stationTemplateToStationInfo(stationTemplate); + stationInfo.hashId = getHashId(this.index, stationTemplate); + stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate); stationInfo.ocppVersion = stationTemplate?.ocppVersion ?? OCPPVersion.VERSION_16; - ChargingStationUtils.createSerialNumber(stationTemplate, stationInfo); - if (Utils.isNotEmptyArray(stationTemplate?.power)) { + createSerialNumber(stationTemplate, stationInfo); + if (isNotEmptyArray(stationTemplate?.power)) { stationTemplate.power = stationTemplate.power as number[]; - const powerArrayRandomIndex = Math.floor(Utils.secureRandom() * stationTemplate.power.length); + const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length); stationInfo.maximumPower = stationTemplate?.powerUnit === PowerUnits.KILO_WATT ? stationTemplate.power[powerArrayRandomIndex] * 1000 @@ -1222,7 +1203,7 @@ export class ChargingStation { stationInfo.firmwareVersionPattern = stationTemplate?.firmwareVersionPattern ?? Constants.SEMVER_PATTERN; if ( - Utils.isNotEmptyString(stationInfo.firmwareVersion) && + isNotEmptyString(stationInfo.firmwareVersion) && new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion) === false ) { logger.warn( @@ -1240,7 +1221,7 @@ export class ChargingStation { }, stationTemplate?.firmwareUpgrade ?? {} ); - stationInfo.resetTime = !Utils.isNullOrUndefined(stationTemplate?.resetTime) + stationInfo.resetTime = !isNullOrUndefined(stationTemplate?.resetTime) ? stationTemplate.resetTime * 1000 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME; stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo); @@ -1268,7 +1249,7 @@ export class ChargingStation { return stationInfoFromFile; } stationInfoFromFile && - ChargingStationUtils.propagateSerialNumber( + propagateSerialNumber( this.getTemplateFromFile(), stationInfoFromFile, stationInfoFromTemplate @@ -1303,10 +1284,10 @@ export class ChargingStation { private initialize(): void { const stationTemplate = this.getTemplateFromFile(); - 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` + checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); + this.configurationFile = join( + dirname(this.templateFile.replace('station-templates', 'configurations')), + `${getHashId(this.index, stationTemplate)}.json` ); const chargingStationConfiguration = this.getConfigurationFromFile(); if ( @@ -1320,8 +1301,8 @@ export class ChargingStation { this.stationInfo = this.getStationInfo(); if ( this.stationInfo.firmwareStatus === FirmwareStatus.Installing && - Utils.isNotEmptyString(this.stationInfo.firmwareVersion) && - Utils.isNotEmptyString(this.stationInfo.firmwareVersionPattern) + isNotEmptyString(this.stationInfo.firmwareVersion) && + isNotEmptyString(this.stationInfo.firmwareVersionPattern) ) { const patternGroup: number | undefined = this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ?? @@ -1331,7 +1312,7 @@ export class ChargingStation { ?.slice(1, patternGroup + 1); const patchLevelIndex = match.length - 1; match[patchLevelIndex] = ( - Utils.convertToInt(match[patchLevelIndex]) + + convertToInt(match[patchLevelIndex]) + this.stationInfo.firmwareUpgrade?.versionUpgrade?.step ).toString(); this.stationInfo.firmwareVersion = match?.join('.'); @@ -1345,9 +1326,7 @@ export class ChargingStation { this.configuredSupervisionUrl ); } - this.bootNotificationRequest = ChargingStationUtils.createBootNotificationRequest( - this.stationInfo - ); + this.bootNotificationRequest = createBootNotificationRequest(this.stationInfo); this.powerDivider = this.getPowerDivider(); // OCPP configuration this.ocppConfiguration = this.getOcppConfiguration(); @@ -1414,7 +1393,7 @@ export class ChargingStation { } if ( this.getSupervisionUrlOcppConfiguration() && - Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) && + isNotEmptyString(this.getSupervisionUrlOcppKey()) && !ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey()) ) { ChargingStationConfigurationUtils.addConfigurationKey( @@ -1425,7 +1404,7 @@ export class ChargingStation { ); } else if ( !this.getSupervisionUrlOcppConfiguration() && - Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) && + isNotEmptyString(this.getSupervisionUrlOcppKey()) && ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey()) ) { ChargingStationConfigurationUtils.deleteConfigurationKey( @@ -1435,7 +1414,7 @@ export class ChargingStation { ); } if ( - Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) && + isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) && !ChargingStationConfigurationUtils.getConfigurationKey( this, this.stationInfo.amperageLimitationOcppKey @@ -1445,8 +1424,7 @@ export class ChargingStation { this, this.stationInfo.amperageLimitationOcppKey, ( - this.stationInfo.maximumAmperage * - ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo) + this.stationInfo.maximumAmperage * getAmperageLimitationUnitDivider(this.stationInfo) ).toString() ); } @@ -1492,14 +1470,14 @@ export class ChargingStation { for (const evseStatus of this.evses.values()) { for (const connectorId of evseStatus.connectors.keys()) { connectorsPhaseRotation.push( - ChargingStationUtils.getPhaseRotationValue(connectorId, this.getNumberOfPhases()) + getPhaseRotationValue(connectorId, this.getNumberOfPhases()) ); } } } else { for (const connectorId of this.connectors.keys()) { connectorsPhaseRotation.push( - ChargingStationUtils.getPhaseRotationValue(connectorId, this.getNumberOfPhases()) + getPhaseRotationValue(connectorId, this.getNumberOfPhases()) ); } } @@ -1555,11 +1533,11 @@ export class ChargingStation { private initializeConnectorsOrEvsesFromFile(configuration: ChargingStationConfiguration): void { if (configuration?.connectorsStatus && !configuration?.evsesStatus) { for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) { - this.connectors.set(connectorId, Utils.cloneObject(connectorStatus)); + this.connectors.set(connectorId, cloneObject(connectorStatus)); } } else if (configuration?.evsesStatus && !configuration?.connectorsStatus) { for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) { - const evseStatus = Utils.cloneObject(evseStatusConfiguration); + const evseStatus = cloneObject(evseStatusConfiguration); delete evseStatus.connectorsStatus; this.evses.set(evseId, { ...(evseStatus as EvseStatus), @@ -1613,13 +1591,8 @@ export class ChargingStation { } if (stationTemplate?.Connectors) { const { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors } = - ChargingStationUtils.checkConnectorsConfiguration( - stationTemplate, - this.logPrefix(), - this.templateFile - ); - const connectorsConfigHash = crypto - .createHash(Constants.DEFAULT_HASH_ALGORITHM) + checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile); + const connectorsConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM) .update( `${JSON.stringify(stationTemplate?.Connectors)}${configuredMaxConnectors.toString()}` ) @@ -1640,18 +1613,18 @@ export class ChargingStation { } const templateConnectorId = connectorId > 0 && stationTemplate?.randomConnectors - ? Utils.getRandomInteger(templateMaxAvailableConnectors, 1) + ? getRandomInteger(templateMaxAvailableConnectors, 1) : connectorId; const connectorStatus = stationTemplate?.Connectors[templateConnectorId]; - ChargingStationUtils.checkStationInfoConnectorStatus( + checkStationInfoConnectorStatus( templateConnectorId, connectorStatus, this.logPrefix(), this.templateFile ); - this.connectors.set(connectorId, Utils.cloneObject(connectorStatus)); + this.connectors.set(connectorId, cloneObject(connectorStatus)); } - ChargingStationUtils.initializeConnectorsMapStatus(this.connectors, this.logPrefix()); + initializeConnectorsMapStatus(this.connectors, this.logPrefix()); this.saveConnectorsStatus(); } else { logger.warn( @@ -1691,8 +1664,7 @@ export class ChargingStation { ); } if (stationTemplate?.Evses) { - const evsesConfigHash = crypto - .createHash(Constants.DEFAULT_HASH_ALGORITHM) + const evsesConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM) .update(JSON.stringify(stationTemplate?.Evses)) .digest('hex'); const evsesConfigChanged = @@ -1700,22 +1672,19 @@ export class ChargingStation { if (this.evses?.size === 0 || evsesConfigChanged) { evsesConfigChanged && this.evses.clear(); this.evsesConfigurationHash = evsesConfigHash; - const templateMaxEvses = ChargingStationUtils.getMaxNumberOfEvses(stationTemplate?.Evses); + const templateMaxEvses = getMaxNumberOfEvses(stationTemplate?.Evses); if (templateMaxEvses > 0) { for (const evse in stationTemplate.Evses) { - const evseId = Utils.convertToInt(evse); + const evseId = convertToInt(evse); this.evses.set(evseId, { - connectors: ChargingStationUtils.buildConnectorsMap( + connectors: buildConnectorsMap( stationTemplate?.Evses[evse]?.Connectors, this.logPrefix(), this.templateFile ), availability: AvailabilityType.Operative, }); - ChargingStationUtils.initializeConnectorsMapStatus( - this.evses.get(evseId)?.connectors, - this.logPrefix() - ); + initializeConnectorsMapStatus(this.evses.get(evseId)?.connectors, this.logPrefix()); } this.saveEvsesStatus(); } else { @@ -1737,7 +1706,7 @@ export class ChargingStation { private getConfigurationFromFile(): ChargingStationConfiguration | undefined { let configuration: ChargingStationConfiguration | undefined; - if (Utils.isNotEmptyString(this.configurationFile) && fs.existsSync(this.configurationFile)) { + if (isNotEmptyString(this.configurationFile) && existsSync(this.configurationFile)) { try { if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) { configuration = this.sharedLRUCache.getChargingStationConfiguration( @@ -1747,7 +1716,7 @@ export class ChargingStation { const measureId = `${FileType.ChargingStationConfiguration} read`; const beginId = PerformanceStatistics.beginMeasure(measureId); configuration = JSON.parse( - fs.readFileSync(this.configurationFile, 'utf8') + readFileSync(this.configurationFile, 'utf8') ) as ChargingStationConfiguration; PerformanceStatistics.endMeasure(measureId, beginId); this.sharedLRUCache.setChargingStationConfiguration(configuration); @@ -1780,13 +1749,13 @@ export class ChargingStation { } private saveConfiguration(): void { - if (Utils.isNotEmptyString(this.configurationFile)) { + if (isNotEmptyString(this.configurationFile)) { try { - if (!fs.existsSync(path.dirname(this.configurationFile))) { - fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true }); + if (!existsSync(dirname(this.configurationFile))) { + mkdirSync(dirname(this.configurationFile), { recursive: true }); } let configurationData: ChargingStationConfiguration = - Utils.cloneObject(this.getConfigurationFromFile()) ?? {}; + cloneObject(this.getConfigurationFromFile()) ?? {}; if (this.getStationInfoPersistentConfiguration() && this.stationInfo) { configurationData.stationInfo = this.stationInfo; } else { @@ -1818,8 +1787,7 @@ export class ChargingStation { delete configurationData.evsesStatus; } delete configurationData.configurationHash; - const configurationHash = crypto - .createHash(Constants.DEFAULT_HASH_ALGORITHM) + const configurationHash = createHash(Constants.DEFAULT_HASH_ALGORITHM) .update( JSON.stringify({ stationInfo: configurationData.stationInfo, @@ -1834,9 +1802,9 @@ export class ChargingStation { configurationData.configurationHash = configurationHash; const measureId = `${FileType.ChargingStationConfiguration} write`; const beginId = PerformanceStatistics.beginMeasure(measureId); - const fileDescriptor = fs.openSync(this.configurationFile, 'w'); - fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8'); - fs.closeSync(fileDescriptor); + const fileDescriptor = openSync(this.configurationFile, 'w'); + writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8'); + closeSync(fileDescriptor); PerformanceStatistics.endMeasure(measureId, beginId); this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash); this.sharedLRUCache.setChargingStationConfiguration(configurationData); @@ -1913,7 +1881,7 @@ export class ChargingStation { }); if (this.isRegistered() === false) { this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount; - await Utils.sleep( + await sleep( this?.bootNotificationResponse?.interval ? this.bootNotificationResponse.interval * 1000 : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL @@ -1950,7 +1918,7 @@ export class ChargingStation { case WebSocketCloseEventStatusCode.CLOSE_NORMAL: case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS: logger.info( - `${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString( + `${this.logPrefix()} WebSocket normally closed with status '${getWebSocketCloseEventStatusString( code )}' and reason '${reason.toString()}'` ); @@ -1959,7 +1927,7 @@ export class ChargingStation { // Abnormal close default: logger.error( - `${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString( + `${this.logPrefix()} WebSocket abnormally closed with status '${getWebSocketCloseEventStatusString( code )}' and reason '${reason.toString()}'` ); @@ -2160,7 +2128,10 @@ export class ChargingStation { private async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise { if (this.hasEvses) { - for (const evseStatus of this.evses.values()) { + for (const [evseId, evseStatus] of this.evses) { + if (evseId === 0) { + continue; + } for (const [connectorId, connectorStatus] of evseStatus.connectors) { if (connectorStatus.transactionStarted === true) { await this.stopTransactionOnConnector(connectorId, reason); @@ -2232,19 +2203,19 @@ export class ChargingStation { private getAmperageLimitation(): number | undefined { if ( - Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) && + isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) && ChargingStationConfigurationUtils.getConfigurationKey( this, this.stationInfo.amperageLimitationOcppKey ) ) { return ( - Utils.convertToInt( + convertToInt( ChargingStationConfigurationUtils.getConfigurationKey( this, this.stationInfo.amperageLimitationOcppKey )?.value - ) / ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo) + ) / getAmperageLimitationUnitDivider(this.stationInfo) ); } } @@ -2267,11 +2238,7 @@ export class ChargingStation { for (const [evseId, evseStatus] of this.evses) { if (evseId > 0) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { - const connectorBootStatus = ChargingStationUtils.getBootConnectorStatus( - this, - connectorId, - connectorStatus - ); + const connectorBootStatus = getBootConnectorStatus(this, connectorId, connectorStatus); await OCPPServiceUtils.sendAndSetConnectorStatus( this, connectorId, @@ -2284,7 +2251,7 @@ export class ChargingStation { } else { for (const connectorId of this.connectors.keys()) { if (connectorId > 0) { - const connectorBootStatus = ChargingStationUtils.getBootConnectorStatus( + const connectorBootStatus = getBootConnectorStatus( this, connectorId, this.getConnectorStatus(connectorId) @@ -2370,7 +2337,7 @@ export class ChargingStation { this, StandardParametersKey.WebSocketPingInterval ) - ? Utils.convertToInt( + ? convertToInt( ChargingStationConfigurationUtils.getConfigurationKey( this, StandardParametersKey.WebSocketPingInterval @@ -2384,13 +2351,13 @@ export class ChargingStation { } }, webSocketPingInterval * 1000); logger.info( - `${this.logPrefix()} WebSocket ping started every ${Utils.formatDurationSeconds( + `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds( webSocketPingInterval )}` ); } else if (this.webSocketPingSetInterval) { logger.info( - `${this.logPrefix()} WebSocket ping already started every ${Utils.formatDurationSeconds( + `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds( webSocketPingInterval )}` ); @@ -2411,11 +2378,11 @@ export class ChargingStation { private getConfiguredSupervisionUrl(): URL { let configuredSupervisionUrl: string; const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls(); - if (Utils.isNotEmptyArray(supervisionUrls)) { + if (isNotEmptyArray(supervisionUrls)) { let configuredSupervisionUrlIndex: number; switch (Configuration.getSupervisionUrlDistribution()) { case SupervisionUrlDistribution.RANDOM: - configuredSupervisionUrlIndex = Math.floor(Utils.secureRandom() * supervisionUrls.length); + configuredSupervisionUrlIndex = Math.floor(secureRandom() * supervisionUrls.length); break; case SupervisionUrlDistribution.ROUND_ROBIN: case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY: @@ -2435,7 +2402,7 @@ export class ChargingStation { } else { configuredSupervisionUrl = supervisionUrls as string; } - if (Utils.isNotEmptyString(configuredSupervisionUrl)) { + if (isNotEmptyString(configuredSupervisionUrl)) { return new URL(configuredSupervisionUrl); } const errorMsg = 'No supervision url(s) configured'; @@ -2476,7 +2443,7 @@ export class ChargingStation { ) { ++this.autoReconnectRetryCount; const reconnectDelay = this.getReconnectExponentialDelay() - ? Utils.exponentialDelay(this.autoReconnectRetryCount) + ? exponentialDelay(this.autoReconnectRetryCount) : this.getConnectionTimeout() * 1000; const reconnectDelayWithdraw = 1000; const reconnectTimeout = @@ -2484,12 +2451,12 @@ export class ChargingStation { ? reconnectDelay - reconnectDelayWithdraw : 0; logger.error( - `${this.logPrefix()} WebSocket connection retry in ${Utils.roundTo( + `${this.logPrefix()} WebSocket connection retry in ${roundTo( reconnectDelay, 2 )}ms, timeout ${reconnectTimeout}ms` ); - await Utils.sleep(reconnectDelay); + await sleep(reconnectDelay); logger.error( `${this.logPrefix()} WebSocket connection retry #${this.autoReconnectRetryCount.toString()}` );