-} from '../types/ChargingStationWorker';
-
-import Configuration from '../utils/Configuration';
-import { StationTemplateUrl } from '../types/ConfigurationData';
-import Statistics from '../types/Statistics';
-import { Storage } from '../performance/storage/Storage';
-import { StorageFactory } from '../performance/storage/StorageFactory';
-import { UIServiceUtils } from './ui-websocket-services/UIServiceUtils';
-import UIWebSocketServer from './UIWebSocketServer';
-import Utils from '../utils/Utils';
-import WorkerAbstract from '../worker/WorkerAbstract';
-import WorkerFactory from '../worker/WorkerFactory';
-import chalk from 'chalk';
-import { isMainThread } from 'worker_threads';
-import path from 'path';
-import { version } from '../../package.json';
-
-export default class Bootstrap {
- private static instance: Bootstrap | null = null;
- private workerImplementation: WorkerAbstract<ChargingStationWorkerData> | null = null;
- private readonly uiWebSocketServer!: UIWebSocketServer;
- private readonly storage!: Storage;
- private numberOfChargingStations: number;
- private readonly version: string = version;
- private started: boolean;
- private readonly workerScript: string;
-
- private constructor() {
- this.started = false;
- this.workerScript = path.join(
- path.resolve(__dirname, '../'),
- 'charging-station',
- 'ChargingStationWorker.js'
- );
- this.initWorkerImplementation();
- Configuration.getUIWebSocketServer().enabled &&
- (this.uiWebSocketServer = new UIWebSocketServer({
- ...Configuration.getUIWebSocketServer().options,
- handleProtocols: UIServiceUtils.handleProtocols,
- }));
- Configuration.getPerformanceStorage().enabled &&
- (this.storage = StorageFactory.getStorage(
- Configuration.getPerformanceStorage().type,
- Configuration.getPerformanceStorage().uri,
- this.logPrefix()
- ));
- Configuration.setConfigurationChangeCallback(async () => Bootstrap.getInstance().restart());
- }
-
- public static getInstance(): Bootstrap {
- if (!Bootstrap.instance) {
- Bootstrap.instance = new Bootstrap();
- }
- return Bootstrap.instance;
- }
-
- public async start(): Promise<void> {
- if (isMainThread && !this.started) {
- try {
- this.numberOfChargingStations = 0;
- await this.storage?.open();
- await this.workerImplementation.start();
- this.uiWebSocketServer?.start();
- const stationTemplateUrls = Configuration.getStationTemplateUrls();
- // Start ChargingStation object in worker thread
- if (stationTemplateUrls) {
- for (const stationTemplateUrl of stationTemplateUrls) {
- try {
- const nbStations = stationTemplateUrl.numberOfStations ?? 0;
- for (let index = 1; index <= nbStations; index++) {
- await this.startChargingStation(index, stationTemplateUrl);
- }
- } catch (error) {
- console.error(
- chalk.red(
- 'Charging station start with template file ' + stationTemplateUrl.file + ' error '
- ),
- error
- );
+ ConfigurationSection,
+ ProcedureName,
+ type SimulatorState,
+ type Statistics,
+ type StorageConfiguration,
+ type TemplateStatistics,
+ type UIServerConfiguration,
+ type WorkerConfiguration
+} from '../types/index.js'
+import {
+ Configuration,
+ Constants,
+ formatDurationMilliSeconds,
+ generateUUID,
+ handleUncaughtException,
+ handleUnhandledRejection,
+ isAsyncFunction,
+ isNotEmptyArray,
+ logger,
+ logPrefix
+} from '../utils/index.js'
+import { DEFAULT_ELEMENTS_PER_WORKER, type WorkerAbstract, WorkerFactory } from '../worker/index.js'
+import { buildTemplateName, waitChargingStationEvents } from './Helpers.js'
+import type { AbstractUIServer } from './ui-server/AbstractUIServer.js'
+import { UIServerFactory } from './ui-server/UIServerFactory.js'
+
+const moduleName = 'Bootstrap'
+
+enum exitCodes {
+ succeeded = 0,
+ missingChargingStationsConfiguration = 1,
+ duplicateChargingStationTemplateUrls = 2,
+ noChargingStationTemplates = 3,
+ gracefulShutdownError = 4
+}
+
+export class Bootstrap extends EventEmitter {
+ private static instance: Bootstrap | null = null
+ private workerImplementation?: WorkerAbstract<ChargingStationWorkerData, ChargingStationInfo>
+ private readonly uiServer: AbstractUIServer
+ private storage?: Storage
+ private readonly templateStatistics: Map<string, TemplateStatistics>
+ private readonly version: string = version
+ private started: boolean
+ private starting: boolean
+ private stopping: boolean
+ private uiServerStarted: boolean
+
+ private constructor () {
+ super()
+ for (const signal of ['SIGINT', 'SIGQUIT', 'SIGTERM']) {
+ process.on(signal, this.gracefulShutdown.bind(this))
+ }
+ // Enable unconditionally for now
+ handleUnhandledRejection()
+ handleUncaughtException()
+ this.started = false
+ this.starting = false
+ this.stopping = false
+ this.uiServerStarted = false
+ this.templateStatistics = new Map<string, TemplateStatistics>()
+ this.uiServer = UIServerFactory.getUIServerImplementation(
+ Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
+ )
+ this.initializeCounters()
+ this.initializeWorkerImplementation(
+ Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
+ )
+ Configuration.configurationChangeCallback = async () => {
+ if (isMainThread) {
+ await Bootstrap.getInstance().restart()
+ }
+ }
+ }
+
+ public static getInstance (): Bootstrap {
+ if (Bootstrap.instance === null) {
+ Bootstrap.instance = new Bootstrap()
+ }
+ return Bootstrap.instance
+ }
+
+ public get numberOfChargingStationTemplates (): number {
+ return this.templateStatistics.size
+ }
+
+ public get numberOfConfiguredChargingStations (): number {
+ return [...this.templateStatistics.values()].reduce(
+ (accumulator, value) => accumulator + value.configured,
+ 0
+ )
+ }
+
+ public get numberOfProvisionedChargingStations (): number {
+ return [...this.templateStatistics.values()].reduce(
+ (accumulator, value) => accumulator + value.provisioned,
+ 0
+ )
+ }
+
+ public getState (): SimulatorState {
+ return {
+ version: this.version,
+ configuration: Configuration.getConfigurationData(),
+ started: this.started,
+ templateStatistics: this.templateStatistics
+ }
+ }
+
+ public getLastIndex (templateName: string): number {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const indexes = [...this.templateStatistics.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 {
+ return this.storage?.getPerformanceStatistics()
+ }
+
+ private get numberOfAddedChargingStations (): number {
+ return [...this.templateStatistics.values()].reduce(
+ (accumulator, value) => accumulator + value.added,
+ 0
+ )
+ }
+
+ private get numberOfStartedChargingStations (): number {
+ return [...this.templateStatistics.values()].reduce(
+ (accumulator, value) => accumulator + value.started,
+ 0
+ )
+ }
+
+ public async start (): Promise<void> {
+ if (!this.started) {
+ 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)
+ this.on(
+ ChargingStationWorkerMessageEvents.performanceStatistics,
+ this.workerEventPerformanceStatistics
+ )
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ if (isAsyncFunction(this.workerImplementation?.start)) {
+ await this.workerImplementation.start()
+ } else {
+ (this.workerImplementation?.start as () => void)()
+ }
+ const performanceStorageConfiguration =
+ Configuration.getConfigurationSection<StorageConfiguration>(
+ ConfigurationSection.performanceStorage
+ )
+ if (performanceStorageConfiguration.enabled === true) {
+ this.storage = StorageFactory.getStorage(
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ performanceStorageConfiguration.type!,
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ performanceStorageConfiguration.uri!,
+ this.logPrefix()
+ )
+ await this.storage?.open()
+ }
+ 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 = stationTemplateUrl.numberOfStations
+ for (let index = 1; index <= nbStations; index++) {
+ await this.addChargingStation(index, stationTemplateUrl.file)