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 getLogEnabled(): boolean | undefined {
238 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logEnabled')
239 ? Configuration
.getConfig()?.logEnabled
243 public static getLogConsole(): boolean | undefined {
244 Configuration
.warnDeprecatedConfigurationKey(
247 "Use 'logConsole' instead"
249 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logConsole')
250 ? Configuration
.getConfig()?.logConsole
254 public static getLogFormat(): string | undefined {
255 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFormat')
256 ? Configuration
.getConfig()?.logFormat
260 public static getLogRotate(): boolean | undefined {
261 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logRotate')
262 ? Configuration
.getConfig()?.logRotate
266 public static getLogMaxFiles(): number | string | false | undefined {
268 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
269 Configuration
.getConfig()?.logMaxFiles
273 public static getLogMaxSize(): number | string | false | undefined {
275 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
276 Configuration
.getConfig()?.logMaxSize
280 public static getLogLevel(): string | undefined {
281 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logLevel')
282 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
286 public static getLogFile(): string | undefined {
287 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFile')
288 ? Configuration
.getConfig()?.logFile
292 public static getLogErrorFile(): string | undefined {
293 Configuration
.warnDeprecatedConfigurationKey(
296 "Use 'logErrorFile' instead"
298 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logErrorFile')
299 ? Configuration
.getConfig()?.logErrorFile
303 public static getSupervisionUrls(): string | string[] | undefined {
304 Configuration
.warnDeprecatedConfigurationKey(
307 "Use 'supervisionUrls' instead"
309 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
310 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
314 return Configuration
.getConfig()?.supervisionUrls
;
317 public static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
318 Configuration
.warnDeprecatedConfigurationKey(
319 'distributeStationToTenantEqually',
321 "Use 'supervisionUrlDistribution' instead"
323 Configuration
.warnDeprecatedConfigurationKey(
324 'distributeStationsToTenantsEqually',
326 "Use 'supervisionUrlDistribution' instead"
328 return Utils
.hasOwnProp(Configuration
.getConfig(), 'supervisionUrlDistribution')
329 ? Configuration
.getConfig()?.supervisionUrlDistribution
330 : SupervisionUrlDistribution
.ROUND_ROBIN
;
333 private static logPrefix
= (): string => {
334 return `${new Date().toLocaleString()} Simulator configuration |`;
337 private static warnDeprecatedConfigurationKey(
339 sectionName
?: string,
344 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
345 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
348 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
349 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
350 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
354 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
356 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
357 `Deprecated configuration key
'${key}' usage$
{
358 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
365 // Read the config file
366 private static getConfig(): ConfigurationData
| null {
367 if (!Configuration
.configuration
) {
369 Configuration
.configuration
= JSON
.parse(
370 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
371 ) as ConfigurationData
;
373 Configuration
.handleFileException(
374 Configuration
.configurationFile
,
375 FileType
.Configuration
,
376 error
as NodeJS
.ErrnoException
,
377 Configuration
.logPrefix()
380 if (!Configuration
.configurationFileWatcher
) {
381 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
384 return Configuration
.configuration
;
387 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
389 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
390 if (filename
?.trim().length
> 0 && event
=== 'change') {
391 // Nullify to force configuration file reading
392 Configuration
.configuration
= null;
393 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
394 Configuration
.configurationChangeCallback().catch((error
) => {
395 throw typeof error
=== 'string' ? new Error(error
) : error
;
401 Configuration
.handleFileException(
402 Configuration
.configurationFile
,
403 FileType
.Configuration
,
404 error
as NodeJS
.ErrnoException
,
405 Configuration
.logPrefix()
410 private static handleFileException(
413 error
: NodeJS
.ErrnoException
,
416 const prefix
= Utils
.isNotEmptyString(logPrefix
) ? `${logPrefix} ` : '';
418 switch (error
.code
) {
420 logMsg
= `${fileType} file ${file} not found:`;
423 logMsg
= `${fileType} file ${file} already exists:`;
426 logMsg
= `${fileType} file ${file} access denied:`;
429 logMsg
= `${fileType} file ${file} permission denied:`;
432 logMsg
= `${fileType} file ${file} error:`;
434 console
.error(`${chalk.green(prefix)}${chalk.red(`${logMsg} `)}`, error);
438 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
439 switch (storageType) {
440 case StorageType.JSON_FILE:
441 return Configuration.buildPerformanceUriFilePath(
442 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
444 case StorageType.SQLITE:
445 return Configuration.buildPerformanceUriFilePath(
446 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
449 throw new Error(`Performance storage URI
is mandatory
with storage
type '${storageType}'`);
453 private static buildPerformanceUriFilePath(file: string) {
454 return `file
://${path.join(
455 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),