X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=5bc0ec9ad24d8ae76f486f388eb86d03a2cad0b8;hb=6a4032b5d8f3cbaa18d3beddcdfe9d335c1cba90;hp=02200e4114a101a512374bbbb1432c857c607069;hpb=15af2feb2facd029a64f005074db43cdb175e69c;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 02200e41..5bc0ec9a 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -22,6 +22,7 @@ import { import { buildConnectorsMap, checkChargingStation, + checkConfiguration, checkConnectorsConfiguration, checkStationInfoConnectorStatus, checkTemplate, @@ -37,11 +38,9 @@ import { getMaxNumberOfEvses, getNumberOfReservableConnectors, getPhaseRotationValue, - hasFeatureProfile, hasReservationExpired, initializeConnectorsMapStatus, propagateSerialNumber, - removeExpiredReservations, stationTemplateToStationInfo, warnTemplateKeysDeprecation, } from './Helpers'; @@ -50,13 +49,16 @@ import { OCPP16IncomingRequestService, OCPP16RequestService, OCPP16ResponseService, - OCPP16ServiceUtils, OCPP20IncomingRequestService, OCPP20RequestService, OCPP20ResponseService, type OCPPIncomingRequestService, type OCPPRequestService, - OCPPServiceUtils, + buildMeterValue, + buildStatusNotificationRequest, + buildTransactionEndMeterValue, + getMessageTypeString, + sendAndSetConnectorStatus, } from './ocpp'; import { SharedLRUCache } from './SharedLRUCache'; import { BaseError, OCPPError } from '../exception'; @@ -89,9 +91,7 @@ import { type HeartbeatResponse, type IncomingRequest, type IncomingRequestCommand, - type JsonType, MessageType, - type MeterValue, MeterValueMeasurand, type MeterValuesRequest, type MeterValuesResponse, @@ -113,7 +113,6 @@ import { type StopTransactionResponse, SupervisionUrlDistribution, SupportedFeatureProfiles, - VendorParametersKey, Voltage, type WSError, WebSocketCloseEventStatusCode, @@ -189,7 +188,7 @@ export class ChargingStation extends EventEmitter { private readonly sharedLRUCache: SharedLRUCache; private webSocketPingSetInterval?: NodeJS.Timeout; private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel; - private reservationExpirationSetInterval?: NodeJS.Timeout; + private flushMessageBufferSetInterval?: NodeJS.Timeout; constructor(index: number, templateFile: string) { super(); @@ -238,14 +237,18 @@ export class ChargingStation extends EventEmitter { } public logPrefix = (): string => { - return logPrefix( - ` ${ - (isNotEmptyString(this?.stationInfo?.chargingStationId) - ? this?.stationInfo?.chargingStationId - : getChargingStationId(this.index, this.getTemplateFromFile()!)) ?? - 'Error at building log prefix' - } |`, - ); + if (isNotEmptyString(this?.stationInfo?.chargingStationId)) { + return logPrefix(` ${this?.stationInfo?.chargingStationId} |`); + } + let stationTemplate: ChargingStationTemplate | undefined; + try { + stationTemplate = JSON.parse( + readFileSync(this.templateFile, 'utf8'), + ) as ChargingStationTemplate; + } catch { + stationTemplate = undefined; + } + return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`); }; public hasIdTags(): boolean { @@ -266,10 +269,6 @@ export class ChargingStation extends EventEmitter { return this?.wsConnection?.readyState === WebSocket.OPEN; } - public getRegistrationStatus(): RegistrationStatusEnumType | undefined { - return this?.bootNotificationResponse?.status; - } - public inUnknownState(): boolean { return isNullOrUndefined(this?.bootNotificationResponse?.status); } @@ -499,7 +498,7 @@ export class ChargingStation extends EventEmitter { } public startHeartbeat(): void { - if (this.getHeartbeatInterval() > 0 && !this.heartbeatSetInterval) { + if (this.getHeartbeatInterval() > 0 && this.heartbeatSetInterval === undefined) { this.heartbeatSetInterval = setInterval(() => { this.ocppRequestService .requestHandler(this, RequestCommand.HEARTBEAT) @@ -515,7 +514,7 @@ export class ChargingStation extends EventEmitter { this.getHeartbeatInterval(), )}`, ); - } else if (this.heartbeatSetInterval) { + } else if (this.heartbeatSetInterval !== undefined) { logger.info( `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds( this.getHeartbeatInterval(), @@ -572,8 +571,7 @@ export class ChargingStation extends EventEmitter { } if (interval > 0) { this.getConnectorStatus(connectorId)!.transactionSetInterval = setInterval(() => { - // FIXME: Implement OCPP version agnostic helpers - const meterValue: MeterValue = OCPP16ServiceUtils.buildMeterValue( + const meterValue = buildMeterValue( this, connectorId, this.getConnectorStatus(connectorId)!.transactionId!, @@ -606,7 +604,7 @@ export class ChargingStation extends EventEmitter { } public stopMeterValues(connectorId: number) { - if (this.getConnectorStatus(connectorId)?.transactionSetInterval) { + if (this.getConnectorStatus(connectorId)?.transactionSetInterval !== undefined) { clearInterval(this.getConnectorStatus(connectorId)?.transactionSetInterval); } } @@ -618,9 +616,6 @@ export class ChargingStation extends EventEmitter { if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.start(); } - if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) { - this.startReservationExpirationSetInterval(); - } this.openWSConnection(); // Monitor charging station template file this.templateFileWatcher = watchJsonFile( @@ -681,9 +676,6 @@ export class ChargingStation extends EventEmitter { if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.stop(); } - if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) { - this.stopReservationExpirationSetInterval(); - } this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash); this.templateFileWatcher?.close(); this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash); @@ -715,6 +707,7 @@ export class ChargingStation extends EventEmitter { public bufferMessage(message: string): void { this.messageBuffer.add(message); + this.setIntervalFlushMessageBuffer(); } public openWSConnection( @@ -854,8 +847,7 @@ export class ChargingStation extends EventEmitter { this.stationInfo?.ocppStrictCompliance === true && this.stationInfo?.outOfOrderEndMeterValues === false ) { - // FIXME: Implement OCPP version agnostic helpers - const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue( + const transactionEndMeterValue = buildTransactionEndMeterValue( this, connectorId, this.getEnergyActiveImportRegisterByTransactionId(transactionId!), @@ -893,7 +885,7 @@ export class ChargingStation extends EventEmitter { await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING); } this.getConnectorStatus(reservation.connectorId)!.reservation = reservation; - await OCPPServiceUtils.sendAndSetConnectorStatus( + await sendAndSetConnectorStatus( this, reservation.connectorId, ConnectorStatusEnum.Reserved, @@ -915,7 +907,7 @@ export class ChargingStation extends EventEmitter { case ReservationTerminationReason.RESERVATION_CANCELED: case ReservationTerminationReason.REPLACE_EXISTING: case ReservationTerminationReason.EXPIRED: - await OCPPServiceUtils.sendAndSetConnectorStatus( + await sendAndSetConnectorStatus( this, reservation.connectorId, ConnectorStatusEnum.Available, @@ -975,31 +967,26 @@ export class ChargingStation extends EventEmitter { return false; } - private startReservationExpirationSetInterval(customInterval?: number): void { - const interval = customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_INTERVAL; - if (interval > 0) { - logger.info( - `${this.logPrefix()} Reservation expiration date checks started every ${formatDurationMilliSeconds( - interval, - )}`, - ); - this.reservationExpirationSetInterval = setInterval((): void => { - removeExpiredReservations(this).catch(Constants.EMPTY_FUNCTION); - }, interval); + private setIntervalFlushMessageBuffer(): void { + if (this.flushMessageBufferSetInterval === undefined) { + this.flushMessageBufferSetInterval = setInterval(() => { + if (this.isWebSocketConnectionOpened() === true && this.inAcceptedState() === true) { + this.flushMessageBuffer(); + } + if (this.messageBuffer.size === 0) { + this.clearIntervalFlushMessageBuffer(); + } + }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL); } } - private stopReservationExpirationSetInterval(): void { - if (!isNullOrUndefined(this.reservationExpirationSetInterval)) { - clearInterval(this.reservationExpirationSetInterval); + private clearIntervalFlushMessageBuffer() { + if (this.flushMessageBufferSetInterval !== undefined) { + clearInterval(this.flushMessageBufferSetInterval); + delete this.flushMessageBufferSetInterval; } } - // private restartReservationExpiryDateSetInterval(): void { - // this.stopReservationExpirationSetInterval(); - // this.startReservationExpirationSetInterval(); - // } - private getNumberOfReservableConnectors(): number { let numberOfReservableConnectors = 0; if (this.hasEvses) { @@ -1038,11 +1025,18 @@ export class ChargingStation extends EventEmitter { isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!); if (isNullOrUndefined(error)) { logger.debug( - `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString( + `${this.logPrefix()} >> Buffered ${getMessageTypeString( messageType, - )} payload sent: ${message}`, + )} OCPP message sent '${JSON.stringify(message)}'`, ); this.messageBuffer.delete(message); + } else { + logger.debug( + `${this.logPrefix()} >> Buffered ${getMessageTypeString( + messageType, + )} OCPP message '${JSON.stringify(message)}' send failed:`, + error, + ); } }); } @@ -1146,29 +1140,7 @@ export class ChargingStation extends EventEmitter { } private getStationInfo(): ChargingStationInfo { - const defaultStationInfo: Partial = { - enableStatistics: false, - remoteAuthorization: true, - currentOutType: CurrentType.AC, - mainVoltageMeterValues: true, - phaseLineToLineVoltageMeterValues: false, - customValueLimitationMeterValues: true, - ocppStrictCompliance: true, - outOfOrderEndMeterValues: false, - beginEndMeterValues: false, - meteringPerTransaction: true, - transactionDataMeterValues: false, - supervisionUrlOcppConfiguration: false, - supervisionUrlOcppKey: VendorParametersKey.ConnectionUrl, - ocppVersion: OCPPVersion.VERSION_16, - ocppPersistentConfiguration: true, - stationInfoPersistentConfiguration: true, - automaticTransactionGeneratorPersistentConfiguration: true, - autoReconnectMaxRetries: -1, - registrationMaxRetries: -1, - reconnectExponentialDelay: false, - stopTransactionsOnStopped: true, - }; + const defaultStationInfo = Constants.DEFAULT_STATION_INFO; const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate(); const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile( stationInfoFromTemplate?.stationInfoPersistentConfiguration, @@ -1213,6 +1185,7 @@ export class ChargingStation extends EventEmitter { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (stationConfiguration?.connectorsStatus || stationConfiguration?.evsesStatus) ) { + checkConfiguration(stationConfiguration, this.logPrefix(), this.configurationFile); this.initializeConnectorsOrEvsesFromFile(stationConfiguration); } else { this.initializeConnectorsOrEvsesFromTemplate(stationTemplate); @@ -1226,15 +1199,17 @@ export class ChargingStation extends EventEmitter { const patternGroup: number | undefined = this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ?? this.stationInfo.firmwareVersion?.split('.').length; - const match = this.stationInfo - .firmwareVersion!.match(new RegExp(this.stationInfo.firmwareVersionPattern!))! - .slice(1, patternGroup! + 1); - const patchLevelIndex = match.length - 1; - match[patchLevelIndex] = ( - convertToInt(match[patchLevelIndex]) + - this.stationInfo.firmwareUpgrade!.versionUpgrade!.step! - ).toString(); - this.stationInfo.firmwareVersion = match?.join('.'); + const match = new RegExp(this.stationInfo.firmwareVersionPattern!) + .exec(this.stationInfo.firmwareVersion!) + ?.slice(1, patternGroup! + 1); + if (!isNullOrUndefined(match)) { + const patchLevelIndex = match!.length - 1; + match![patchLevelIndex] = ( + convertToInt(match![patchLevelIndex]) + + this.stationInfo.firmwareUpgrade!.versionUpgrade!.step! + ).toString(); + this.stationInfo.firmwareVersion = match!.join('.'); + } } this.saveStationInfo(); this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl(); @@ -1640,9 +1615,9 @@ export class ChargingStation extends EventEmitter { } if ( this.stationInfo?.ocppPersistentConfiguration === true && - this.ocppConfiguration?.configurationKey + Array.isArray(this.ocppConfiguration?.configurationKey) ) { - configurationData.configurationKey = this.ocppConfiguration.configurationKey; + configurationData.configurationKey = this.ocppConfiguration?.configurationKey; } else { delete configurationData.configurationKey; } @@ -1826,11 +1801,11 @@ export class ChargingStation extends EventEmitter { } throw new OCPPError( ErrorType.PROTOCOL_ERROR, - `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString( + `Cached request for message id ${messageId} ${getMessageTypeString( messageType, )} is not an array`, undefined, - cachedRequest as JsonType, + cachedRequest, ); } @@ -2056,8 +2031,8 @@ export class ChargingStation extends EventEmitter { return powerDivider; } - private getMaximumAmperage(stationInfo: ChargingStationInfo): number | undefined { - const maximumPower = this.getMaximumPower(stationInfo); + private getMaximumAmperage(stationInfo?: ChargingStationInfo): number | undefined { + const maximumPower = (stationInfo ?? this.stationInfo).maximumPower!; switch (this.getCurrentOutType(stationInfo)) { case CurrentType.AC: return ACElectricUtils.amperagePerPhaseFromPower( @@ -2070,10 +2045,6 @@ export class ChargingStation extends EventEmitter { } } - private getMaximumPower(stationInfo?: ChargingStationInfo): number { - return (stationInfo ?? this.stationInfo).maximumPower!; - } - private getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType { return (stationInfo ?? this.stationInfo).currentOutType ?? CurrentType.AC; } @@ -2117,12 +2088,7 @@ export class ChargingStation extends EventEmitter { if (evseId > 0) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { const connectorBootStatus = getBootConnectorStatus(this, connectorId, connectorStatus); - await OCPPServiceUtils.sendAndSetConnectorStatus( - this, - connectorId, - connectorBootStatus, - evseId, - ); + await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus, evseId); } } } @@ -2134,7 +2100,7 @@ export class ChargingStation extends EventEmitter { connectorId, this.getConnectorStatus(connectorId)!, ); - await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorBootStatus); + await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus); } } } @@ -2179,7 +2145,7 @@ export class ChargingStation extends EventEmitter { >( this, RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( + buildStatusNotificationRequest( this, connectorId, ConnectorStatusEnum.Unavailable, @@ -2199,11 +2165,7 @@ export class ChargingStation extends EventEmitter { >( this, RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( - this, - connectorId, - ConnectorStatusEnum.Unavailable, - ), + buildStatusNotificationRequest(this, connectorId, ConnectorStatusEnum.Unavailable), ); delete this.getConnectorStatus(connectorId)?.status; } @@ -2218,7 +2180,7 @@ export class ChargingStation extends EventEmitter { getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value, ) : 0; - if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) { + if (webSocketPingInterval > 0 && this.webSocketPingSetInterval === undefined) { this.webSocketPingSetInterval = setInterval(() => { if (this.isWebSocketConnectionOpened() === true) { this.wsConnection?.ping(); @@ -2229,7 +2191,7 @@ export class ChargingStation extends EventEmitter { webSocketPingInterval, )}`, ); - } else if (this.webSocketPingSetInterval) { + } else if (this.webSocketPingSetInterval !== undefined) { logger.info( `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds( webSocketPingInterval, @@ -2243,7 +2205,7 @@ export class ChargingStation extends EventEmitter { } private stopWebSocketPing(): void { - if (this.webSocketPingSetInterval) { + if (this.webSocketPingSetInterval !== undefined) { clearInterval(this.webSocketPingSetInterval); delete this.webSocketPingSetInterval; } @@ -2288,7 +2250,7 @@ export class ChargingStation extends EventEmitter { } private stopHeartbeat(): void { - if (this.heartbeatSetInterval) { + if (this.heartbeatSetInterval !== undefined) { clearInterval(this.heartbeatSetInterval); delete this.heartbeatSetInterval; }