X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=e217991ac8aad3c7c4256010687929df0f0162a3;hb=9388120364d5a2f3b9103f05802e1756d2dad04f;hp=2d934e066ff07a58470dc6162c342491df1adb93;hpb=d627f8ef31d97da80f4de5ed0cb9386472f51bbe;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 2d934e06..e217991a 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -120,8 +120,9 @@ import { createSerialNumber, getAmperageLimitationUnitDivider, getBootConnectorStatus, - getChargingStationConnectorChargingProfilesPowerLimit, + getChargingStationChargingProfilesLimit, getChargingStationId, + getConnectorChargingProfilesLimit, getDefaultVoltageOut, getHashId, getIdTagsFile, @@ -131,6 +132,7 @@ import { hasFeatureProfile, hasReservationExpired, initializeConnectorsMapStatus, + prepareConnectorStatus, propagateSerialNumber, setChargingStationOptions, stationTemplateToStationInfo, @@ -181,7 +183,6 @@ export class ChargingStation extends EventEmitter { private ocppIncomingRequestService!: OCPPIncomingRequestService private readonly messageBuffer: Set private configuredSupervisionUrl!: URL - private wsConnectionRetried: boolean private wsConnectionRetryCount: number private templateFileWatcher?: FSWatcher private templateFileHash!: string @@ -196,7 +197,6 @@ export class ChargingStation extends EventEmitter { this.starting = false this.stopping = false this.wsConnection = null - this.wsConnectionRetried = false this.wsConnectionRetryCount = 0 this.index = index this.templateFile = templateFile @@ -225,16 +225,21 @@ export class ChargingStation extends EventEmitter { }) this.on(ChargingStationEvents.accepted, () => { this.startMessageSequence( - this.wsConnectionRetried + this.wsConnectionRetryCount > 0 ? true : this.getAutomaticTransactionGeneratorConfiguration()?.stopAbsoluteDuration ).catch((error: unknown) => { logger.error(`${this.logPrefix()} Error while starting the message sequence:`, error) }) - this.wsConnectionRetried = false + this.wsConnectionRetryCount = 0 }) this.on(ChargingStationEvents.rejected, () => { - this.wsConnectionRetried = false + this.wsConnectionRetryCount = 0 + }) + this.on(ChargingStationEvents.connected, () => { + if (this.wsPingSetInterval == null) { + this.startWebSocketPing() + } }) this.on(ChargingStationEvents.disconnected, () => { try { @@ -387,14 +392,14 @@ export class ChargingStation extends EventEmitter { } public getConnectorMaximumAvailablePower (connectorId: number): number { - let connectorAmperageLimitationPowerLimit: number | undefined + let connectorAmperageLimitationLimit: number | undefined const amperageLimitation = this.getAmperageLimitation() if ( amperageLimitation != null && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion amperageLimitation < this.stationInfo!.maximumAmperage! ) { - connectorAmperageLimitationPowerLimit = + connectorAmperageLimitationLimit = (this.stationInfo?.currentOutType === CurrentType.AC ? ACElectricUtils.powerTotal( this.getNumberOfPhases(), @@ -410,20 +415,25 @@ export class ChargingStation extends EventEmitter { } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const connectorMaximumPower = this.stationInfo!.maximumPower! / this.powerDivider! - const connectorChargingProfilesPowerLimit = - getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId) + const chargingStationChargingProfilesLimit = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + getChargingStationChargingProfilesLimit(this)! / this.powerDivider! + const connectorChargingProfilesLimit = getConnectorChargingProfilesLimit(this, connectorId) return min( isNaN(connectorMaximumPower) ? Number.POSITIVE_INFINITY : connectorMaximumPower, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - isNaN(connectorAmperageLimitationPowerLimit!) + isNaN(connectorAmperageLimitationLimit!) ? Number.POSITIVE_INFINITY : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - connectorAmperageLimitationPowerLimit!, + connectorAmperageLimitationLimit!, + isNaN(chargingStationChargingProfilesLimit) + ? Number.POSITIVE_INFINITY + : chargingStationChargingProfilesLimit, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - isNaN(connectorChargingProfilesPowerLimit!) + isNaN(connectorChargingProfilesLimit!) ? Number.POSITIVE_INFINITY : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - connectorChargingProfilesPowerLimit! + connectorChargingProfilesLimit! ) } @@ -554,7 +564,8 @@ export class ChargingStation extends EventEmitter { } public startHeartbeat (): void { - if (this.getHeartbeatInterval() > 0 && this.heartbeatSetInterval == null) { + const heartbeatInterval = this.getHeartbeatInterval() + if (heartbeatInterval > 0 && this.heartbeatSetInterval == null) { this.heartbeatSetInterval = setInterval(() => { this.ocppRequestService .requestHandler(this, RequestCommand.HEARTBEAT) @@ -564,21 +575,21 @@ export class ChargingStation extends EventEmitter { error ) }) - }, this.getHeartbeatInterval()) + }, heartbeatInterval) logger.info( `${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds( - this.getHeartbeatInterval() + heartbeatInterval )}` ) } else if (this.heartbeatSetInterval != null) { logger.info( `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds( - this.getHeartbeatInterval() + heartbeatInterval )}` ) } else { logger.error( - `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat` + `${this.logPrefix()} Heartbeat interval set to ${heartbeatInterval}, not starting the heartbeat` ) } } @@ -666,6 +677,11 @@ export class ChargingStation extends EventEmitter { } } + public restartMeterValues (connectorId: number, interval: number): void { + this.stopMeterValues(connectorId) + this.startMeterValues(connectorId, interval) + } + private add (): void { this.emit(ChargingStationEvents.added) } @@ -1468,7 +1484,10 @@ export class ChargingStation extends EventEmitter { private initializeConnectorsOrEvsesFromFile (configuration: ChargingStationConfiguration): void { if (configuration.connectorsStatus != null && configuration.evsesStatus == null) { for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) { - this.connectors.set(connectorId, clone(connectorStatus)) + this.connectors.set( + connectorId, + prepareConnectorStatus(clone(connectorStatus)) + ) } } else if (configuration.evsesStatus != null && configuration.connectorsStatus == null) { for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) { @@ -1480,7 +1499,7 @@ export class ChargingStation extends EventEmitter { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion evseStatusConfiguration.connectorsStatus!.map((connectorStatus, connectorId) => [ connectorId, - connectorStatus + prepareConnectorStatus(connectorStatus) ]) ) }) @@ -1822,6 +1841,7 @@ export class ChargingStation extends EventEmitter { private async onOpen (): Promise { if (this.isWebSocketConnectionOpened()) { + this.emit(ChargingStationEvents.connected) this.emit(ChargingStationEvents.updated) logger.info( `${this.logPrefix()} Connection to OCPP server through ${ @@ -1832,20 +1852,16 @@ export class ChargingStation extends EventEmitter { if (!this.isRegistered()) { // Send BootNotification do { - // FIXME: duplicated assignment with the boot notification response handler - this.bootNotificationResponse = await this.ocppRequestService.requestHandler< + await this.ocppRequestService.requestHandler< BootNotificationRequest, BootNotificationResponse >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, { skipBufferingOnError: true }) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (this.bootNotificationResponse?.currentTime != null) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.bootNotificationResponse.currentTime = convertToDate( - this.bootNotificationResponse.currentTime - )! - } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.bootNotificationResponse!.currentTime = convertToDate( + this.bootNotificationResponse?.currentTime + )! if (!this.isRegistered()) { this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount await sleep( @@ -1869,7 +1885,6 @@ export class ChargingStation extends EventEmitter { })` ) } - this.wsConnectionRetryCount = 0 this.emit(ChargingStationEvents.updated) } else { logger.warn( @@ -1921,7 +1936,7 @@ export class ChargingStation extends EventEmitter { } throw new OCPPError( ErrorType.PROTOCOL_ERROR, - `Cached request for message id ${messageId} ${getMessageTypeString( + `Cached request for message id '${messageId}' ${getMessageTypeString( messageType )} is not an array`, undefined, @@ -1931,6 +1946,14 @@ export class ChargingStation extends EventEmitter { private async handleIncomingMessage (request: IncomingRequest): Promise { const [messageType, messageId, commandName, commandPayload] = request + if (this.requests.has(messageId)) { + throw new OCPPError( + ErrorType.SECURITY_ERROR, + `Received message with duplicate message id '${messageId}'`, + commandName, + commandPayload + ) + } if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.addRequestStatistic(commandName, messageType) } @@ -1955,7 +1978,7 @@ export class ChargingStation extends EventEmitter { // Error throw new OCPPError( ErrorType.INTERNAL_ERROR, - `Response for unknown message id ${messageId}`, + `Response for unknown message id '${messageId}'`, undefined, commandPayload ) @@ -1980,7 +2003,7 @@ export class ChargingStation extends EventEmitter { // Error throw new OCPPError( ErrorType.INTERNAL_ERROR, - `Error response for unknown message id ${messageId}`, + `Error response for unknown message id '${messageId}'`, undefined, { errorType, errorMessage, errorDetails } ) @@ -2065,10 +2088,10 @@ export class ChargingStation extends EventEmitter { } if (!(error instanceof OCPPError)) { logger.warn( - `${this.logPrefix()} Error thrown at incoming OCPP command '${ + `${this.logPrefix()} Error thrown at incoming OCPP command ${ commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND // eslint-disable-next-line @typescript-eslint/no-base-to-string - }' message '${data.toString()}' handling is not an OCPPError:`, + } message '${data.toString()}' handling is not an OCPPError:`, error ) } @@ -2222,12 +2245,12 @@ export class ChargingStation extends EventEmitter { }) } // Start WebSocket ping - this.startWebSocketPing() + if (this.wsPingSetInterval == null) { + this.startWebSocketPing() + } // Start heartbeat if (this.heartbeatSetInterval == null) { this.startHeartbeat() - } else if (this.getHeartbeatInterval() !== this.bootNotificationResponse?.interval) { - this.restartHeartbeat() } // Initialize connectors status if (this.hasEvses) { @@ -2318,13 +2341,14 @@ export class ChargingStation extends EventEmitter { } } + private getWebSocketPingInterval (): number { + return getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) != null + ? convertToInt(getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value) + : 0 + } + private startWebSocketPing (): void { - const webSocketPingInterval = - getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) != null - ? convertToInt( - getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value - ) - : 0 + const webSocketPingInterval = this.getWebSocketPingInterval() if (webSocketPingInterval > 0 && this.wsPingSetInterval == null) { this.wsPingSetInterval = setInterval(() => { if (this.isWebSocketConnectionOpened()) { @@ -2414,7 +2438,6 @@ export class ChargingStation extends EventEmitter { this.wsConnectionRetryCount < this.stationInfo!.autoReconnectMaxRetries! || this.stationInfo?.autoReconnectMaxRetries === -1 ) { - this.wsConnectionRetried = true ++this.wsConnectionRetryCount const reconnectDelay = this.stationInfo?.reconnectExponentialDelay === true @@ -2441,9 +2464,7 @@ export class ChargingStation extends EventEmitter { ) } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) { logger.error( - `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${ - this.wsConnectionRetryCount - }) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries})` + `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${this.wsConnectionRetryCount.toString()}) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries?.toString()})` ) } }