import { type Storage, StorageFactory } from '../performance/index.js'
import {
type ChargingStationData,
+ type ChargingStationOptions,
type ChargingStationWorkerData,
type ChargingStationWorkerEventError,
type ChargingStationWorkerMessage,
isAsyncFunction,
isNotEmptyArray,
logPrefix,
- logger,
- max
+ logger
} from '../utils/index.js'
import { type WorkerAbstract, WorkerFactory } from '../worker/index.js'
configured: number
added: number
started: number
- lastIndex: number
+ indexes: Set<number>
}
export class Bootstrap extends EventEmitter {
private static instance: Bootstrap | null = null
private workerImplementation?: WorkerAbstract<ChargingStationWorkerData>
- private readonly uiServer?: AbstractUIServer
+ private readonly uiServer: AbstractUIServer
private storage?: Storage
- private readonly chargingStationsByTemplate: Map<string, TemplateChargingStations>
+ private readonly templatesChargingStations: Map<string, TemplateChargingStations>
private readonly version: string = version
private initializedCounters: boolean
private started: boolean
private starting: boolean
private stopping: boolean
+ private uiServerStarted: boolean
private constructor () {
super()
this.started = false
this.starting = false
this.stopping = false
- this.chargingStationsByTemplate = new Map<string, TemplateChargingStations>()
+ this.uiServerStarted = false
this.uiServer = UIServerFactory.getUIServerImplementation(
Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
)
+ this.templatesChargingStations = new Map<string, TemplateChargingStations>()
this.initializedCounters = false
this.initializeCounters()
Configuration.configurationChangeCallback = async () => {
}
public get numberOfChargingStationTemplates (): number {
- return this.chargingStationsByTemplate.size
+ return this.templatesChargingStations.size
}
public get numberOfConfiguredChargingStations (): number {
- return [...this.chargingStationsByTemplate.values()].reduce(
+ return [...this.templatesChargingStations.values()].reduce(
(accumulator, value) => accumulator + value.configured,
0
)
}
public getLastIndex (templateName: string): number {
- return this.chargingStationsByTemplate.get(templateName)?.lastIndex ?? 0
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const indexes = [...this.templatesChargingStations.get(templateName)!.indexes]
+ .concat(0)
+ .sort((a, b) => a - b)
+ for (let i = 0; i < indexes.length - 1; i++) {
+ if (indexes[i + 1] - indexes[i] !== 1) {
+ return indexes[i]
+ }
+ }
+ return indexes[indexes.length - 1]
}
public getPerformanceStatistics (): IterableIterator<Statistics> | undefined {
}
private get numberOfAddedChargingStations (): number {
- return [...this.chargingStationsByTemplate.values()].reduce(
+ return [...this.templatesChargingStations.values()].reduce(
(accumulator, value) => accumulator + value.added,
0
)
}
private get numberOfStartedChargingStations (): number {
- return [...this.chargingStationsByTemplate.values()].reduce(
+ return [...this.templatesChargingStations.values()].reduce(
(accumulator, value) => accumulator + value.started,
0
)
if (!this.starting) {
this.starting = true
this.on(ChargingStationWorkerMessageEvents.added, this.workerEventAdded)
+ this.on(ChargingStationWorkerMessageEvents.deleted, this.workerEventDeleted)
this.on(ChargingStationWorkerMessageEvents.started, this.workerEventStarted)
this.on(ChargingStationWorkerMessageEvents.stopped, this.workerEventStopped)
this.on(ChargingStationWorkerMessageEvents.updated, this.workerEventUpdated)
ChargingStationWorkerMessageEvents.workerElementError,
(eventError: ChargingStationWorkerEventError) => {
logger.error(
- `${this.logPrefix()} ${moduleName}.messageHandler: Error occurred while handling '${eventError.event}' event on worker:`,
+ `${this.logPrefix()} ${moduleName}.start: Error occurred while handling '${eventError.event}' event on worker:`,
eventError
)
}
)
await this.storage?.open()
}
- Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
- .enabled === true && this.uiServer?.start()
+ if (
+ !this.uiServerStarted &&
+ Configuration.getConfigurationSection<UIServerConfiguration>(
+ ConfigurationSection.uiServer
+ ).enabled === true
+ ) {
+ this.uiServer.start()
+ this.uiServerStarted = true
+ }
// Start ChargingStation object instance in worker thread
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
for (const stationTemplateUrl of Configuration.getStationTemplateUrls()!) {
try {
const nbStations =
- this.chargingStationsByTemplate.get(parse(stationTemplateUrl.file).name)
- ?.configured ?? stationTemplateUrl.numberOfStations
+ this.templatesChargingStations.get(parse(stationTemplateUrl.file).name)?.configured ??
+ stationTemplateUrl.numberOfStations
for (let index = 1; index <= nbStations; index++) {
await this.addChargingStation(index, stationTemplateUrl.file)
}
if (this.started) {
if (!this.stopping) {
this.stopping = true
- await this.uiServer?.sendInternalRequest(
+ await this.uiServer.sendInternalRequest(
this.uiServer.buildProtocolRequest(
generateUUID(),
ProcedureName.STOP_CHARGING_STATION,
await this.workerImplementation?.stop()
delete this.workerImplementation
this.removeAllListeners()
+ this.uiServer.clearCaches()
+ this.initializedCounters = false
await this.storage?.close()
delete this.storage
this.started = false
private async restart (): Promise<void> {
await this.stop()
- Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
- .enabled !== true && this.uiServer?.stop()
- this.initializedCounters = false
+ if (
+ this.uiServerStarted &&
+ Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
+ .enabled !== true
+ ) {
+ this.uiServer.stop()
+ this.uiServerStarted = false
+ }
await this.start()
}
if (!isMainThread) {
return
}
- let elementsPerWorker: number | undefined
+ let elementsPerWorker: number
switch (workerConfiguration.elementsPerWorker) {
+ case 'all':
+ elementsPerWorker = this.numberOfConfiguredChargingStations
+ break
case 'auto':
+ default:
elementsPerWorker =
this.numberOfConfiguredChargingStations > availableParallelism()
? Math.round(this.numberOfConfiguredChargingStations / (availableParallelism() * 1.5))
: 1
break
- case 'all':
- elementsPerWorker = this.numberOfConfiguredChargingStations
- break
}
this.workerImplementation = WorkerFactory.getWorkerImplementation<ChargingStationWorkerData>(
join(
poolMaxSize: workerConfiguration.poolMaxSize!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
poolMinSize: workerConfiguration.poolMinSize!,
- elementsPerWorker: elementsPerWorker ?? (workerConfiguration.elementsPerWorker as number),
+ elementsPerWorker,
poolOptions: {
messageHandler: this.messageHandler.bind(this) as MessageHandler<Worker>,
workerOptions: { resourceLimits: workerConfiguration.resourceLimits }
case ChargingStationWorkerMessageEvents.added:
this.emit(ChargingStationWorkerMessageEvents.added, msg.data)
break
+ case ChargingStationWorkerMessageEvents.deleted:
+ this.emit(ChargingStationWorkerMessageEvents.deleted, msg.data)
+ break
case ChargingStationWorkerMessageEvents.started:
this.emit(ChargingStationWorkerMessageEvents.started, msg.data)
break
}
private readonly workerEventAdded = (data: ChargingStationData): void => {
- this.uiServer?.chargingStations.set(data.stationInfo.hashId, data)
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- ++this.chargingStationsByTemplate.get(data.stationInfo.templateName)!.added
+ this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
logger.info(
`${this.logPrefix()} ${moduleName}.workerEventAdded: Charging station ${
data.stationInfo.chargingStationId
)
}
+ private readonly workerEventDeleted = (data: ChargingStationData): void => {
+ this.uiServer.chargingStations.delete(data.stationInfo.hashId)
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const templateChargingStations = this.templatesChargingStations.get(
+ data.stationInfo.templateName
+ )!
+ --templateChargingStations.added
+ templateChargingStations.indexes.delete(data.stationInfo.templateIndex)
+ logger.info(
+ `${this.logPrefix()} ${moduleName}.workerEventDeleted: Charging station ${
+ data.stationInfo.chargingStationId
+ } (hashId: ${data.stationInfo.hashId}) deleted (${
+ this.numberOfAddedChargingStations
+ } added from ${this.numberOfConfiguredChargingStations} configured charging station(s))`
+ )
+ }
+
private readonly workerEventStarted = (data: ChargingStationData): void => {
- this.uiServer?.chargingStations.set(data.stationInfo.hashId, data)
+ this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- ++this.chargingStationsByTemplate.get(data.stationInfo.templateName)!.started
+ ++this.templatesChargingStations.get(data.stationInfo.templateName)!.started
logger.info(
`${this.logPrefix()} ${moduleName}.workerEventStarted: Charging station ${
data.stationInfo.chargingStationId
}
private readonly workerEventStopped = (data: ChargingStationData): void => {
- this.uiServer?.chargingStations.set(data.stationInfo.hashId, data)
+ this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- --this.chargingStationsByTemplate.get(data.stationInfo.templateName)!.started
+ --this.templatesChargingStations.get(data.stationInfo.templateName)!.started
logger.info(
`${this.logPrefix()} ${moduleName}.workerEventStopped: Charging station ${
data.stationInfo.chargingStationId
}
private readonly workerEventUpdated = (data: ChargingStationData): void => {
- this.uiServer?.chargingStations.set(data.stationInfo.hashId, data)
+ this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
}
private readonly workerEventPerformanceStatistics = (data: Statistics): void => {
if (isNotEmptyArray(stationTemplateUrls)) {
for (const stationTemplateUrl of stationTemplateUrls) {
const templateName = parse(stationTemplateUrl.file).name
- this.chargingStationsByTemplate.set(templateName, {
+ this.templatesChargingStations.set(templateName, {
configured: stationTemplateUrl.numberOfStations,
added: 0,
started: 0,
- lastIndex: 0
+ indexes: new Set<number>()
})
- this.uiServer?.chargingStationTemplates.add(templateName)
+ this.uiServer.chargingStationTemplates.add(templateName)
}
- if (this.chargingStationsByTemplate.size !== stationTemplateUrls.length) {
+ if (this.templatesChargingStations.size !== stationTemplateUrls.length) {
console.error(
chalk.red(
"'stationTemplateUrls' contains duplicate entries, please check your configuration"
}
}
- public async addChargingStation (index: number, stationTemplateFile: string): Promise<void> {
+ public async addChargingStation (
+ index: number,
+ stationTemplateFile: string,
+ options?: ChargingStationOptions
+ ): Promise<void> {
await this.workerImplementation?.addElement({
index,
templateFile: join(
'assets',
'station-templates',
stationTemplateFile
- )
+ ),
+ options
})
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- this.chargingStationsByTemplate.get(parse(stationTemplateFile).name)!.lastIndex = max(
- index,
- this.chargingStationsByTemplate.get(parse(stationTemplateFile).name)?.lastIndex ?? -Infinity
- )
+ const templateChargingStations = this.templatesChargingStations.get(
+ parse(stationTemplateFile).name
+ )!
+ ++templateChargingStations.added
+ templateChargingStations.indexes.add(index)
}
private gracefulShutdown (): void {
this.stop()
.then(() => {
console.info(chalk.green('Graceful shutdown'))
- this.uiServer?.stop()
+ this.uiServer.stop()
+ this.uiServerStarted = false
this.waitChargingStationsStopped()
.then(() => {
exit(exitCodes.succeeded)