X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;ds=inline;f=src%2Fcharging-station%2FChargingStation.ts;h=c42ff2b9625b126cdfccf0ce9f5df145315d644d;hb=a65319ba9924b96204d7bfd3cbdcf8b84c113768;hp=a47bc68182a77de166b1dc9fd273a7d31d59265b;hpb=f90c1757e0620ff455e9cd0f90245872b9d77b01;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index a47bc681..c42ff2b9 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -84,14 +84,14 @@ import type OCPPRequestService from './ocpp/OCPPRequestService'; import SharedLRUCache from './SharedLRUCache'; export default class ChargingStation { - public hashId!: string; public readonly templateFile: string; - public authorizedTagsCache: AuthorizedTagsCache; public stationInfo!: ChargingStationInfo; - public stopped: boolean; - public readonly connectors: Map; + public started: boolean; + public authorizedTagsCache: AuthorizedTagsCache; + public automaticTransactionGenerator!: AutomaticTransactionGenerator; public ocppConfiguration!: ChargingStationOcppConfiguration; public wsConnection!: WebSocket; + public readonly connectors: Map; public readonly requests: Map; public performanceStatistics!: PerformanceStatistics; public heartbeatSetInterval!: NodeJS.Timeout; @@ -110,22 +110,21 @@ export default class ChargingStation { private autoReconnectRetryCount: number; private templateFileWatcher!: fs.FSWatcher; private readonly sharedLRUCache: SharedLRUCache; - private automaticTransactionGenerator!: AutomaticTransactionGenerator; private webSocketPingSetInterval!: NodeJS.Timeout; private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel; constructor(index: number, templateFile: string) { this.index = index; this.templateFile = templateFile; - this.stopped = false; - this.wsConnectionRestarted = false; - this.autoReconnectRetryCount = 0; - this.sharedLRUCache = SharedLRUCache.getInstance(); - this.authorizedTagsCache = AuthorizedTagsCache.getInstance(); this.connectors = new Map(); this.requests = new Map(); this.messageBuffer = new Set(); + this.sharedLRUCache = SharedLRUCache.getInstance(); + this.authorizedTagsCache = AuthorizedTagsCache.getInstance(); this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this); + this.started = false; + this.wsConnectionRestarted = false; + this.autoReconnectRetryCount = 0; this.initialize(); } @@ -332,22 +331,18 @@ export default class ChargingStation { } } - public getEnergyActiveImportRegisterByTransactionId(transactionId: number): number { - const transactionConnectorStatus = this.getConnectorStatus( - this.getConnectorIdByTransactionId(transactionId) + public getEnergyActiveImportRegisterByTransactionId( + transactionId: number, + meterStop = false + ): number { + return this.getEnergyActiveImportRegister( + this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)), + meterStop ); - if (this.getMeteringPerTransaction()) { - return transactionConnectorStatus?.transactionEnergyActiveImportRegisterValue ?? 0; - } - return transactionConnectorStatus?.energyActiveImportRegisterValue ?? 0; } public getEnergyActiveImportRegisterByConnectorId(connectorId: number): number { - const connectorStatus = this.getConnectorStatus(connectorId); - if (this.getMeteringPerTransaction()) { - return connectorStatus?.transactionEnergyActiveImportRegisterValue ?? 0; - } - return connectorStatus?.energyActiveImportRegisterValue ?? 0; + return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId)); } public getAuthorizeRemoteTxRequests(): boolean { @@ -430,13 +425,13 @@ export default class ChargingStation { ); return; } - if (!this.getConnectorStatus(connectorId)?.transactionStarted) { + if (this.getConnectorStatus(connectorId)?.transactionStarted === false) { logger.error( `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started` ); return; } else if ( - this.getConnectorStatus(connectorId)?.transactionStarted && + this.getConnectorStatus(connectorId)?.transactionStarted === true && !this.getConnectorStatus(connectorId)?.transactionId ) { logger.error( @@ -524,9 +519,7 @@ export default class ChargingStation { parentPort.postMessage(MessageChannelUtils.buildStartedMessage(this)); } - public async stop(reason: StopTransactionReason = StopTransactionReason.NONE): Promise { - // Stop message sequence - await this.stopMessageSequence(reason); + public async stop(): Promise { for (const connectorId of this.connectors.keys()) { if (connectorId > 0) { await this.ocppRequestService.requestHandler< @@ -548,12 +541,12 @@ export default class ChargingStation { this.templateFileWatcher.close(); this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash); this.bootNotificationResponse = null; - this.stopped = true; + this.started = false; parentPort.postMessage(MessageChannelUtils.buildStoppedMessage(this)); } - public async reset(reason?: StopTransactionReason): Promise { - await this.stop(reason); + public async reset(): Promise { + await this.stop(); await Utils.sleep(this.stationInfo.resetTime); this.initialize(); this.start(); @@ -601,7 +594,6 @@ export default class ChargingStation { ? limit : DCElectricUtils.power(this.getVoltageOut(), limit); } - const connectorMaximumPower = this.getMaximumPower() / this.powerDivider; if (limit > connectorMaximumPower) { logger.error( @@ -748,25 +740,89 @@ export default class ChargingStation { } } - public startAutomaticTransactionGenerator(): void { + public startAutomaticTransactionGenerator(connectorIds?: number[]): void { if (!this.automaticTransactionGenerator) { this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance( this.getAutomaticTransactionGeneratorConfigurationFromTemplate(), this ); } - if (!this.automaticTransactionGenerator.started) { + if (!Utils.isEmptyArray(connectorIds)) { + for (const connectorId of connectorIds) { + this.automaticTransactionGenerator.startConnector(connectorId); + } + } else { this.automaticTransactionGenerator.start(); } } - public stopAutomaticTransactionGenerator(): void { - if (this.automaticTransactionGenerator?.started) { - this.automaticTransactionGenerator.stop(); + public stopAutomaticTransactionGenerator(connectorIds?: number[]): void { + if (!Utils.isEmptyArray(connectorIds)) { + for (const connectorId of connectorIds) { + this.automaticTransactionGenerator?.stopConnector(connectorId); + } + } else { + this.automaticTransactionGenerator?.stop(); this.automaticTransactionGenerator = null; } } + public getNumberOfRunningTransactions(): number { + let trxCount = 0; + for (const connectorId of this.connectors.keys()) { + if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) { + trxCount++; + } + } + return trxCount; + } + + public async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise { + for (const connectorId of this.connectors.keys()) { + if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) { + await this.stopTransactionOnConnector(connectorId, reason); + } + } + } + + public async stopTransactionOnConnector( + connectorId: number, + reason = StopTransactionReason.NONE + ): Promise { + const transactionId = this.getConnectorStatus(connectorId).transactionId; + if ( + this.getBeginEndMeterValues() && + this.getOcppStrictCompliance() && + !this.getOutOfOrderEndMeterValues() + ) { + // FIXME: Implement OCPP version agnostic helpers + const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue( + this, + connectorId, + this.getEnergyActiveImportRegisterByTransactionId(transactionId) + ); + await this.ocppRequestService.requestHandler( + this, + RequestCommand.METER_VALUES, + { + connectorId, + transactionId, + meterValue: [transactionEndMeterValue], + } + ); + } + return this.ocppRequestService.requestHandler( + this, + RequestCommand.STOP_TRANSACTION, + { + transactionId, + meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true), + idTag: this.getTransactionIdTag(transactionId), + reason, + } + ); + } + private flushMessageBuffer(): void { if (this.messageBuffer.size > 0) { this.messageBuffer.forEach((message) => { @@ -841,6 +897,7 @@ export default class ChargingStation { ); const stationInfo: ChargingStationInfo = ChargingStationUtils.stationTemplateToStationInfo(stationTemplate); + stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate); stationInfo.chargingStationId = ChargingStationUtils.getChargingStationId( this.index, stationTemplate @@ -950,20 +1007,19 @@ export default class ChargingStation { } private initialize(): void { - this.hashId = ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile()); - logger.info(`${this.logPrefix()} Charging station hashId '${this.hashId}'`); this.configurationFile = path.join( path.dirname(this.templateFile.replace('station-templates', 'configurations')), - this.hashId + '.json' + ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile()) + '.json' ); this.stationInfo = this.getStationInfo(); this.saveStationInfo(); + logger.info(`${this.logPrefix()} Charging station hashId '${this.stationInfo.hashId}'`); // Avoid duplication of connectors related information in RAM this.stationInfo?.Connectors && delete this.stationInfo.Connectors; this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl(); if (this.getEnableStatistics()) { this.performanceStatistics = PerformanceStatistics.getInstance( - this.hashId, + this.stationInfo.hashId, this.stationInfo.chargingStationId, this.configuredSupervisionUrl ); @@ -1230,7 +1286,7 @@ export default class ChargingStation { } // Initialize transaction attributes on connectors for (const connectorId of this.connectors.keys()) { - if (connectorId > 0 && !this.getConnectorStatus(connectorId)?.transactionStarted) { + if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === false) { this.initializeConnectorStatus(connectorId); } } @@ -1389,9 +1445,10 @@ export default class ChargingStation { `${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})` ); } - this.stopped && (this.stopped = false); + this.started === false && (this.started = true); this.autoReconnectRetryCount = 0; this.wsConnectionRestarted = false; + parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this)); } else { logger.warn( `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed` @@ -1405,22 +1462,24 @@ export default class ChargingStation { case WebSocketCloseEventStatusCode.CLOSE_NORMAL: case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS: logger.info( - `${this.logPrefix()} WebSocket normally closed with status '${ChargingStationUtils.getWebSocketCloseEventStatusString( + `${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString( code )}' and reason '${reason}'` ); this.autoReconnectRetryCount = 0; + await this.stopMessageSequence(StopTransactionReason.OTHER); break; // Abnormal close default: logger.error( - `${this.logPrefix()} WebSocket abnormally closed with status '${ChargingStationUtils.getWebSocketCloseEventStatusString( + `${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString( code )}' and reason '${reason}'` ); await this.reconnect(code); break; } + parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this)); } private async onMessage(data: Data): Promise { @@ -1578,6 +1637,24 @@ export default class ChargingStation { logger.error(this.logPrefix() + ' WebSocket error:', error); } + private getEnergyActiveImportRegister( + connectorStatus: ConnectorStatus, + meterStop = false + ): number { + if (this.getMeteringPerTransaction()) { + return ( + (meterStop === true + ? Math.round(connectorStatus?.transactionEnergyActiveImportRegisterValue) + : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0 + ); + } + return ( + (meterStop === true + ? Math.round(connectorStatus?.energyActiveImportRegisterValue) + : connectorStatus?.energyActiveImportRegisterValue) ?? 0 + ); + } + private getUseConnectorId0(stationInfo?: ChargingStationInfo): boolean | undefined { const localStationInfo = stationInfo ?? this.stationInfo; return !Utils.isUndefined(localStationInfo.useConnectorId0) @@ -1585,16 +1662,6 @@ export default class ChargingStation { : true; } - private getNumberOfRunningTransactions(): number { - let trxCount = 0; - for (const connectorId of this.connectors.keys()) { - if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted) { - trxCount++; - } - } - return trxCount; - } - // 0 for disabling private getConnectionTimeout(): number | undefined { if ( @@ -1711,7 +1778,7 @@ export default class ChargingStation { if (connectorId === 0) { continue; } else if ( - !this.stopped && + this.started === true && !this.getConnectorStatus(connectorId)?.status && this.getConnectorStatus(connectorId)?.bootStatus ) { @@ -1727,7 +1794,7 @@ export default class ChargingStation { this.getConnectorStatus(connectorId).status = this.getConnectorStatus(connectorId).bootStatus; } else if ( - this.stopped && + this.started === false && this.getConnectorStatus(connectorId)?.status && this.getConnectorStatus(connectorId)?.bootStatus ) { @@ -1742,7 +1809,7 @@ export default class ChargingStation { }); this.getConnectorStatus(connectorId).status = this.getConnectorStatus(connectorId).bootStatus; - } else if (!this.stopped && this.getConnectorStatus(connectorId)?.status) { + } else if (this.started === true && this.getConnectorStatus(connectorId)?.status) { // Send previous status at template reload await this.ocppRequestService.requestHandler< StatusNotificationRequest, @@ -1783,41 +1850,7 @@ export default class ChargingStation { if (this.automaticTransactionGenerator?.started) { this.stopAutomaticTransactionGenerator(); } else { - for (const connectorId of this.connectors.keys()) { - if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted) { - const transactionId = this.getConnectorStatus(connectorId).transactionId; - if ( - this.getBeginEndMeterValues() && - this.getOcppStrictCompliance() && - !this.getOutOfOrderEndMeterValues() - ) { - // FIXME: Implement OCPP version agnostic helpers - const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue( - this, - connectorId, - this.getEnergyActiveImportRegisterByTransactionId(transactionId) - ); - await this.ocppRequestService.requestHandler( - this, - RequestCommand.METER_VALUES, - { - connectorId, - transactionId, - meterValue: [transactionEndMeterValue], - } - ); - } - await this.ocppRequestService.requestHandler< - StopTransactionRequest, - StopTransactionResponse - >(this, RequestCommand.STOP_TRANSACTION, { - transactionId, - meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId), - idTag: this.getTransactionIdTag(transactionId), - reason, - }); - } - } + await this.stopRunningTransactions(reason); } } }