1 import fs from
'node:fs';
2 import path from
'node:path';
3 import { fileURLToPath
} from
'node:url';
5 import chalk from
'chalk';
6 import merge from
'just-merge';
7 import { WorkerChoiceStrategies
} from
'poolifier';
9 import { Constants
} from
'./Constants';
10 import { Utils
} from
'./Utils';
13 type ConfigurationData
,
15 type StationTemplateUrl
,
16 type StorageConfiguration
,
18 SupervisionUrlDistribution
,
19 type UIServerConfiguration
,
20 type WorkerConfiguration
,
22 import { WorkerConstants
, WorkerProcessType
} from
'../worker';
24 export class Configuration
{
25 private static configurationFile
= path
.join(
26 path
.dirname(fileURLToPath(import.meta
.url
)),
31 private static configurationFileWatcher
: fs
.FSWatcher
| undefined;
32 private static configuration
: ConfigurationData
| null = null;
33 private static configurationChangeCallback
: () => Promise
<void>;
35 private constructor() {
36 // This is intentional
39 public static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
40 Configuration
.configurationChangeCallback
= cb
;
43 public static getLogStatisticsInterval(): number | undefined {
44 Configuration
.warnDeprecatedConfigurationKey(
45 'statisticsDisplayInterval',
47 "Use 'logStatisticsInterval' instead"
50 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logStatisticsInterval')
51 ? Configuration
.getConfig()?.logStatisticsInterval
52 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
55 public static getUIServer(): UIServerConfiguration
{
56 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiWebSocketServer')) {
58 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
59 "Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead"
63 let uiServerConfiguration
: UIServerConfiguration
= {
65 type: ApplicationProtocol
.WS
,
67 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
68 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
71 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiServer')) {
72 uiServerConfiguration
= merge
<UIServerConfiguration
>(
73 uiServerConfiguration
,
74 Configuration
.getConfig()?.uiServer
77 if (Utils
.isCFEnvironment() === true) {
78 delete uiServerConfiguration
.options
?.host
;
79 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
81 return uiServerConfiguration
;
84 public static getPerformanceStorage(): StorageConfiguration
{
85 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
86 let storageConfiguration
: StorageConfiguration
= {
88 type: StorageType
.JSON_FILE
,
89 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
91 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'performanceStorage')) {
92 storageConfiguration
= {
93 ...storageConfiguration
,
94 ...Configuration
.getConfig()?.performanceStorage
,
95 ...(Configuration
.getConfig()?.performanceStorage
?.type === StorageType
.JSON_FILE
&&
96 Configuration
.getConfig()?.performanceStorage
?.uri
&& {
97 uri
: Configuration
.buildPerformanceUriFilePath(
98 new URL(Configuration
.getConfig()?.performanceStorage
?.uri
).pathname
103 return storageConfiguration
;
106 public static getAutoReconnectMaxRetries(): number | undefined {
107 Configuration
.warnDeprecatedConfigurationKey(
108 'autoReconnectTimeout',
110 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
112 Configuration
.warnDeprecatedConfigurationKey(
115 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
117 Configuration
.warnDeprecatedConfigurationKey(
118 'autoReconnectMaxRetries',
120 'Use it in charging station template instead'
123 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
124 return Configuration
.getConfig()?.autoReconnectMaxRetries
;
128 public static getStationTemplateUrls(): StationTemplateUrl
[] | undefined {
129 Configuration
.warnDeprecatedConfigurationKey(
130 'stationTemplateURLs',
132 "Use 'stationTemplateUrls' instead"
134 !Utils
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
135 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
136 'stationTemplateURLs'
137 ] as StationTemplateUrl
[]);
138 Configuration
.getConfig().stationTemplateUrls
.forEach(
139 (stationTemplateUrl
: StationTemplateUrl
) => {
140 if (!Utils
.isUndefined(stationTemplateUrl
['numberOfStation'])) {
142 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
143 `Deprecated configuration key
'numberOfStation' usage
for template file
'${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use
'numberOfStations' instead
`
150 return Configuration
.getConfig()?.stationTemplateUrls
;
153 public static getWorker(): WorkerConfiguration
{
154 Configuration
.warnDeprecatedConfigurationKey(
157 "Use 'worker' section to define the type of worker process model instead"
159 Configuration
.warnDeprecatedConfigurationKey(
162 "Use 'worker' section to define the type of worker process model instead"
164 Configuration
.warnDeprecatedConfigurationKey(
167 "Use 'worker' section to define the worker start delay instead"
169 Configuration
.warnDeprecatedConfigurationKey(
170 'chargingStationsPerWorker',
172 "Use 'worker' section to define the number of element(s) per worker instead"
174 Configuration
.warnDeprecatedConfigurationKey(
177 "Use 'worker' section to define the worker's element start delay instead"
179 Configuration
.warnDeprecatedConfigurationKey(
182 "Use 'worker' section to define the worker pool minimum size instead"
184 Configuration
.warnDeprecatedConfigurationKey(
187 "Use 'worker' section to define the worker pool maximum size instead"
189 Configuration
.warnDeprecatedConfigurationKey(
190 'workerPoolMaxSize;',
192 "Use 'worker' section to define the worker pool maximum size instead"
194 Configuration
.warnDeprecatedConfigurationKey(
195 'workerPoolStrategy;',
197 "Use 'worker' section to define the worker pool strategy instead"
199 let workerConfiguration
: WorkerConfiguration
= {
200 processType
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerProcess')
201 ? Configuration
.getConfig()?.workerProcess
202 : WorkerProcessType
.workerSet
,
203 startDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerStartDelay')
204 ? Configuration
.getConfig()?.workerStartDelay
205 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
206 elementsPerWorker
: Utils
.hasOwnProp(Configuration
.getConfig(), 'chargingStationsPerWorker')
207 ? Configuration
.getConfig()?.chargingStationsPerWorker
208 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
209 elementStartDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'elementStartDelay')
210 ? Configuration
.getConfig()?.elementStartDelay
211 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
212 poolMinSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMinSize')
213 ? Configuration
.getConfig()?.workerPoolMinSize
214 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
215 poolMaxSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMaxSize')
216 ? Configuration
.getConfig()?.workerPoolMaxSize
217 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
219 Configuration
.getConfig()?.workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
221 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'worker')) {
222 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig()?.worker
};
224 return workerConfiguration
;
227 public static workerPoolInUse(): boolean {
228 return [WorkerProcessType
.dynamicPool
, WorkerProcessType
.staticPool
].includes(
229 Configuration
.getWorker().processType
233 public static workerDynamicPoolInUse(): boolean {
234 return Configuration
.getWorker().processType
=== WorkerProcessType
.dynamicPool
;
237 public static getLogConsole(): boolean | undefined {
238 Configuration
.warnDeprecatedConfigurationKey(
241 "Use 'logConsole' instead"
243 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logConsole')
244 ? Configuration
.getConfig()?.logConsole
248 public static getLogFormat(): string | undefined {
249 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFormat')
250 ? Configuration
.getConfig()?.logFormat
254 public static getLogRotate(): boolean | undefined {
255 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logRotate')
256 ? Configuration
.getConfig()?.logRotate
260 public static getLogMaxFiles(): number | string | false | undefined {
262 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
263 Configuration
.getConfig()?.logMaxFiles
267 public static getLogMaxSize(): number | string | false | undefined {
269 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
270 Configuration
.getConfig()?.logMaxSize
274 public static getLogLevel(): string | undefined {
275 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logLevel')
276 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
280 public static getLogFile(): string | undefined {
281 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFile')
282 ? Configuration
.getConfig()?.logFile
286 public static getLogErrorFile(): string | undefined {
287 Configuration
.warnDeprecatedConfigurationKey(
290 "Use 'logErrorFile' instead"
292 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logErrorFile')
293 ? Configuration
.getConfig()?.logErrorFile
297 public static getSupervisionUrls(): string | string[] | undefined {
298 Configuration
.warnDeprecatedConfigurationKey(
301 "Use 'supervisionUrls' instead"
303 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
304 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
308 return Configuration
.getConfig()?.supervisionUrls
;
311 public static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
312 Configuration
.warnDeprecatedConfigurationKey(
313 'distributeStationToTenantEqually',
315 "Use 'supervisionUrlDistribution' instead"
317 Configuration
.warnDeprecatedConfigurationKey(
318 'distributeStationsToTenantsEqually',
320 "Use 'supervisionUrlDistribution' instead"
322 return Utils
.hasOwnProp(Configuration
.getConfig(), 'supervisionUrlDistribution')
323 ? Configuration
.getConfig()?.supervisionUrlDistribution
324 : SupervisionUrlDistribution
.ROUND_ROBIN
;
327 private static logPrefix
= (): string => {
328 return `${new Date().toLocaleString()} Simulator configuration |`;
331 private static warnDeprecatedConfigurationKey(
333 sectionName
?: string,
338 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
339 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
342 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
343 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
344 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
348 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
350 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
351 `Deprecated configuration key
'${key}' usage$
{
352 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
359 // Read the config file
360 private static getConfig(): ConfigurationData
| null {
361 if (!Configuration
.configuration
) {
363 Configuration
.configuration
= JSON
.parse(
364 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
365 ) as ConfigurationData
;
367 Configuration
.handleFileException(
368 Configuration
.configurationFile
,
369 FileType
.Configuration
,
370 error
as NodeJS
.ErrnoException
,
371 Configuration
.logPrefix()
374 if (!Configuration
.configurationFileWatcher
) {
375 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
378 return Configuration
.configuration
;
381 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
383 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
384 if (filename
?.trim().length
> 0 && event
=== 'change') {
385 // Nullify to force configuration file reading
386 Configuration
.configuration
= null;
387 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
388 Configuration
.configurationChangeCallback().catch((error
) => {
389 throw typeof error
=== 'string' ? new Error(error
) : error
;
395 Configuration
.handleFileException(
396 Configuration
.configurationFile
,
397 FileType
.Configuration
,
398 error
as NodeJS
.ErrnoException
,
399 Configuration
.logPrefix()
404 private static handleFileException(
407 error
: NodeJS
.ErrnoException
,
410 const prefix
= Utils
.isNotEmptyString(logPrefix
) ? `${logPrefix} ` : '';
412 switch (error
.code
) {
414 logMsg
= `${fileType} file ${file} not found:`;
417 logMsg
= `${fileType} file ${file} already exists:`;
420 logMsg
= `${fileType} file ${file} access denied:`;
423 logMsg
= `${fileType} file ${file} permission denied:`;
426 logMsg
= `${fileType} file ${file} error:`;
428 console
.error(`${chalk.green(prefix)}${chalk.red(`${logMsg} `)}`, error);
432 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
433 switch (storageType) {
434 case StorageType.JSON_FILE:
435 return Configuration.buildPerformanceUriFilePath(
436 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
438 case StorageType.SQLITE:
439 return Configuration.buildPerformanceUriFilePath(
440 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
443 throw new Error(`Performance storage URI
is mandatory
with storage
type '${storageType}'`);
447 private static buildPerformanceUriFilePath(file: string) {
448 return `file
://${path.join(
449 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),