throw new BaseError(`Connector ${connectorId.toString()} does not exist`)
}
if (this.connectorsStatus.get(connectorId)?.start === false) {
- this.internalStartConnector(connectorId, stopAbsoluteDuration).catch(Constants.EMPTY_FUNCTION)
+ this.internalStartConnector(connectorId, stopAbsoluteDuration).catch((error: unknown) =>
+ logger.error(`${this.logPrefix(connectorId)} Error while starting connector:`, error)
+ )
} else if (this.connectorsStatus.get(connectorId)?.start === true) {
logger.warn(`${this.logPrefix(connectorId)} is already started on connector`)
}
this.uiServerStarted = false
}
this.initializeCounters()
- // FIXME: initialize worker implementation only if the worker section has changed
+ // TODO: compare worker configuration hash to skip unnecessary re-initialization
this.initializeWorkerImplementation(
Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
)
this.storage.storePerformanceStatistics as (
performanceStatistics: Statistics
) => Promise<void>
- )(data).catch(Constants.EMPTY_FUNCTION)
+ )(data).catch((error: unknown) => {
+ logger.error(`${this.logPrefix()} Error while storing performance statistics:`, error)
+ })
} else {
;(this.storage?.storePerformanceStatistics as (performanceStatistics: Statistics) => void)(
data
// Handle WebSocket message
this.wsConnection.on('message', data => {
- this.onMessage(data).catch(Constants.EMPTY_FUNCTION)
+ this.onMessage(data).catch((error: unknown) =>
+ logger.error(`${this.logPrefix()} Error while processing WebSocket message:`, error)
+ )
})
// Handle WebSocket error
this.wsConnection.on('error', this.onError.bind(this))
} else {
this.performanceStatistics?.stop()
}
- // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
+ this.restartHeartbeat()
+ this.restartWebSocketPing()
} catch (error) {
logger.error(
`${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
save?: boolean
}
+const matchesConfigurationKey = (
+ configElement: ConfigurationKey,
+ key: ConfigurationKeyType,
+ caseInsensitive: boolean
+): boolean =>
+ caseInsensitive
+ ? configElement.key.toLowerCase() === key.toLowerCase()
+ : configElement.key === key
+
export const getConfigurationKey = (
chargingStation: ChargingStation,
key: ConfigurationKeyType,
): ConfigurationKey | undefined => {
if (!Array.isArray(chargingStation.ocppConfiguration?.configurationKey)) return undefined
return chargingStation.ocppConfiguration.configurationKey.find(configElement =>
- caseInsensitive
- ? configElement.key.toLowerCase() === key.toLowerCase()
- : configElement.key === key
+ matchesConfigurationKey(configElement, key, caseInsensitive)
)
}
return -1
}
return chargingStation.ocppConfiguration.configurationKey.findIndex(configElement =>
- caseInsensitive
- ? configElement.key.toLowerCase() === key.toLowerCase()
- : configElement.key === key
+ matchesConfigurationKey(configElement, key, caseInsensitive)
)
}
chargingStation.removeReservation(reservation, ReservationTerminationReason.EXPIRED)
)
)
+ let failureCount = 0
for (const result of results) {
if (result.status === 'rejected') {
+ ++failureCount
logger.warn(
`${chargingStation.logPrefix()} ${moduleName}.removeExpiredReservations: reservation removal failed: ${String(result.reason)}`
)
}
}
+ if (failureCount > 0) {
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.removeExpiredReservations: ${failureCount.toString()}/${reservations.length.toString()} expired reservation removal(s) failed`
+ )
+ }
}
export const getNumberOfReservableConnectors = (
deprecatedKey: string,
key?: string
): void => {
- if (template[deprecatedKey as keyof ChargingStationTemplate] != null) {
+ const templateRecord = template as unknown as Record<string, unknown>
+ if (templateRecord[deprecatedKey] != null) {
if (key != null) {
- ;(template as unknown as Record<string, unknown>)[key] =
- template[deprecatedKey as keyof ChargingStationTemplate]
+ templateRecord[key] = templateRecord[deprecatedKey]
}
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
- delete template[deprecatedKey as keyof ChargingStationTemplate]
+ delete templateRecord[deprecatedKey]
}
}
)
delete chargingSchedule.startSchedule
}
- if (connectorStatus?.transactionStarted === true) {
- chargingSchedule.startSchedule = connectorStatus.transactionStart
+ if (connectorStatus?.transactionStarted !== true) {
+ logger.debug(
+ `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfileId} has no active transaction, cannot be evaluated`
+ )
+ return false
+ }
+ chargingSchedule.startSchedule = connectorStatus.transactionStart
+ if (chargingSchedule.startSchedule == null) {
+ logger.warn(
+ `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfileId} has active transaction without start date`
+ )
+ return false
+ }
+ if (chargingSchedule.duration != null) {
+ const elapsedSeconds = differenceInSeconds(currentDate, chargingSchedule.startSchedule)
+ if (elapsedSeconds > chargingSchedule.duration) {
+ logger.debug(
+ `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfileId} duration ${chargingSchedule.duration.toString()}s exceeded (elapsed: ${elapsedSeconds.toString()}s)`
+ )
+ return false
+ }
}
- // FIXME: handle relative charging profile duration
break
}
return true
type UIServerConfiguration,
type UUIDv4,
} from '../../types/index.js'
-import { isEmpty, logger } from '../../utils/index.js'
+import { isEmpty, isNotEmptyString, logger, logPrefix } from '../../utils/index.js'
import { UIServiceFactory } from './ui-services/UIServiceFactory.js'
import { isValidCredential } from './UIServerSecurity.js'
import { getUsernameAndPasswordFromAuthorizationToken } from './UIServerUtils.js'
protected readonly httpServer: Http2Server | Server
protected readonly responseHandlers: Map<UUIDv4, ServerResponse | WebSocket>
+ protected abstract readonly uiServerType: string
+
protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>
private readonly chargingStations: Map<string, ChargingStationData>
return [...this.chargingStations.values()]
}
- public abstract logPrefix (moduleName?: string, methodName?: string, prefixSuffix?: string): string
+ public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
+ const logMsgPrefix =
+ prefixSuffix != null ? `${this.uiServerType} ${prefixSuffix}` : this.uiServerType
+ const logMsg =
+ isNotEmptyString(modName) && isNotEmptyString(methodName)
+ ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
+ : ` ${logMsgPrefix} |`
+ return logPrefix(logMsg)
+ }
public async sendInternalRequest (request: ProtocolRequest): Promise<ProtocolResponse> {
const protocolVersion = ProtocolVersion['0.0.1']
type UIServerConfiguration,
type UUIDv4,
} from '../../types/index.js'
-import {
- generateUUID,
- isNotEmptyString,
- JSONStringify,
- logger,
- logPrefix,
-} from '../../utils/index.js'
+import { generateUUID, JSONStringify, logger } from '../../utils/index.js'
import { AbstractUIServer } from './AbstractUIServer.js'
import {
createBodySizeLimiter,
}
export class UIHttpServer extends AbstractUIServer {
+ protected override readonly uiServerType = 'UI HTTP Server'
+
private readonly acceptsGzip: Map<UUIDv4, boolean>
public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
this.acceptsGzip = new Map<UUIDv4, boolean>()
}
- public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
- const logMsgPrefix = prefixSuffix != null ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server'
- const logMsg =
- isNotEmptyString(modName) && isNotEmptyString(methodName)
- ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
- : ` ${logMsgPrefix} |`
- return logPrefix(logMsg)
- }
-
public sendRequest (request: ProtocolRequest): void {
switch (this.uiServerConfiguration.version) {
case ApplicationProtocolVersion.VERSION_20:
-import chalk from 'chalk'
-
import type { AbstractUIServer } from './AbstractUIServer.js'
import { BaseError } from '../../exception/index.js'
) {
const logMsg = `Non loopback address in '${ConfigurationSection.uiServer}' configuration section without authentication enabled. This is not recommended`
logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`)
- console.warn(chalk.yellow(logMsg))
}
if (
uiServerConfiguration.type === ApplicationProtocol.WS &&
) {
const logMsg = `Only version ${ApplicationProtocolVersion.VERSION_11} with application protocol type '${uiServerConfiguration.type}' is supported in '${ConfigurationSection.uiServer}' configuration section. Falling back to version ${ApplicationProtocolVersion.VERSION_11}`
logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`)
- console.warn(chalk.yellow(logMsg))
uiServerConfiguration.version = ApplicationProtocolVersion.VERSION_11
}
switch (uiServerConfiguration.type) {
ApplicationProtocol.WS
}'`
logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`)
- console.warn(logMsg)
}
return new UIWebSocketServer(uiServerConfiguration)
}
return timingSafeEqual(paddedProvided, paddedExpected)
} catch {
+ // Intentional: fail closed on any comparison error (e.g., encoding issues)
return false
}
}
} from '../../types/index.js'
import {
getWebSocketCloseEventStatusString,
- isNotEmptyString,
JSONStringify,
logger,
- logPrefix,
validateUUID,
} from '../../utils/index.js'
import { AbstractUIServer } from './AbstractUIServer.js'
const moduleName = 'UIWebSocketServer'
export class UIWebSocketServer extends AbstractUIServer {
+ protected override readonly uiServerType = 'UI WebSocket Server'
+
private readonly webSocketServer: WebSocketServer
public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
})
}
- public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
- const logMsgPrefix =
- prefixSuffix != null ? `UI WebSocket Server ${prefixSuffix}` : 'UI WebSocket Server'
- const logMsg =
- isNotEmptyString(modName) && isNotEmptyString(methodName)
- ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
- : ` ${logMsgPrefix} |`
- return logPrefix(logMsg)
- }
-
public sendRequest (request: ProtocolRequest): void {
this.broadcastToClients(JSON.stringify(request))
}