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 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
40 Configuration
.configurationChangeCallback
= cb
;
43 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 static getUIServer(): UIServerConfiguration
{
56 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiWebSocketServer')) {
58 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
61 let uiServerConfiguration
: UIServerConfiguration
= {
63 type: ApplicationProtocol
.WS
,
65 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
66 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
69 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiServer')) {
70 uiServerConfiguration
= merge
<UIServerConfiguration
>(
71 uiServerConfiguration
,
72 Configuration
.getConfig()?.uiServer
75 if (Utils
.isCFEnvironment() === true) {
76 delete uiServerConfiguration
.options
?.host
;
77 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
79 return uiServerConfiguration
;
82 static getPerformanceStorage(): StorageConfiguration
{
83 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
84 let storageConfiguration
: StorageConfiguration
= {
86 type: StorageType
.JSON_FILE
,
87 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
89 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'performanceStorage')) {
90 storageConfiguration
= {
91 ...storageConfiguration
,
92 ...Configuration
.getConfig()?.performanceStorage
,
93 ...(Configuration
.getConfig()?.performanceStorage
?.type === StorageType
.JSON_FILE
&&
94 Configuration
.getConfig()?.performanceStorage
?.uri
&& {
95 uri
: Configuration
.buildPerformanceUriFilePath(
96 new URL(Configuration
.getConfig()?.performanceStorage
?.uri
).pathname
101 return storageConfiguration
;
104 static getAutoReconnectMaxRetries(): number | undefined {
105 Configuration
.warnDeprecatedConfigurationKey(
106 'autoReconnectTimeout',
108 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
110 Configuration
.warnDeprecatedConfigurationKey(
113 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
115 Configuration
.warnDeprecatedConfigurationKey(
116 'autoReconnectMaxRetries',
118 'Use it in charging station template instead'
121 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
122 return Configuration
.getConfig()?.autoReconnectMaxRetries
;
126 static getStationTemplateUrls(): StationTemplateUrl
[] | undefined {
127 Configuration
.warnDeprecatedConfigurationKey(
128 'stationTemplateURLs',
130 "Use 'stationTemplateUrls' instead"
132 !Utils
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
133 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
134 'stationTemplateURLs'
135 ] as StationTemplateUrl
[]);
136 Configuration
.getConfig().stationTemplateUrls
.forEach(
137 (stationTemplateUrl
: StationTemplateUrl
) => {
138 if (!Utils
.isUndefined(stationTemplateUrl
['numberOfStation'])) {
140 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
141 stationTemplateUrl.file
142 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
148 return Configuration
.getConfig()?.stationTemplateUrls
;
151 static getWorker(): WorkerConfiguration
{
152 Configuration
.warnDeprecatedConfigurationKey(
155 "Use 'worker' section to define the type of worker process model instead"
157 Configuration
.warnDeprecatedConfigurationKey(
160 "Use 'worker' section to define the type of worker process model instead"
162 Configuration
.warnDeprecatedConfigurationKey(
165 "Use 'worker' section to define the worker start delay instead"
167 Configuration
.warnDeprecatedConfigurationKey(
168 'chargingStationsPerWorker',
170 "Use 'worker' section to define the number of element(s) per worker instead"
172 Configuration
.warnDeprecatedConfigurationKey(
175 "Use 'worker' section to define the worker's element start delay instead"
177 Configuration
.warnDeprecatedConfigurationKey(
180 "Use 'worker' section to define the worker pool minimum size instead"
182 Configuration
.warnDeprecatedConfigurationKey(
185 "Use 'worker' section to define the worker pool maximum size instead"
187 Configuration
.warnDeprecatedConfigurationKey(
188 'workerPoolMaxSize;',
190 "Use 'worker' section to define the worker pool maximum size instead"
192 Configuration
.warnDeprecatedConfigurationKey(
193 'workerPoolStrategy;',
195 "Use 'worker' section to define the worker pool strategy instead"
197 let workerConfiguration
: WorkerConfiguration
= {
198 processType
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerProcess')
199 ? Configuration
.getConfig()?.workerProcess
200 : WorkerProcessType
.workerSet
,
201 startDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerStartDelay')
202 ? Configuration
.getConfig()?.workerStartDelay
203 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
204 elementsPerWorker
: Utils
.hasOwnProp(Configuration
.getConfig(), 'chargingStationsPerWorker')
205 ? Configuration
.getConfig()?.chargingStationsPerWorker
206 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
207 elementStartDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'elementStartDelay')
208 ? Configuration
.getConfig()?.elementStartDelay
209 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
210 poolMinSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMinSize')
211 ? Configuration
.getConfig()?.workerPoolMinSize
212 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
213 poolMaxSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMaxSize')
214 ? Configuration
.getConfig()?.workerPoolMaxSize
215 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
217 Configuration
.getConfig()?.workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
219 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'worker')) {
220 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig()?.worker
};
222 return workerConfiguration
;
225 static getLogConsole(): boolean | undefined {
226 Configuration
.warnDeprecatedConfigurationKey(
229 "Use 'logConsole' instead"
231 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logConsole')
232 ? Configuration
.getConfig()?.logConsole
236 static getLogFormat(): string | undefined {
237 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFormat')
238 ? Configuration
.getConfig()?.logFormat
242 static getLogRotate(): boolean | undefined {
243 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logRotate')
244 ? Configuration
.getConfig()?.logRotate
248 static getLogMaxFiles(): number | string | false | undefined {
250 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
251 Configuration
.getConfig()?.logMaxFiles
255 static getLogMaxSize(): number | string | false | undefined {
257 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
258 Configuration
.getConfig()?.logMaxSize
262 static getLogLevel(): string | undefined {
263 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logLevel')
264 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
268 static getLogFile(): string | undefined {
269 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFile')
270 ? Configuration
.getConfig()?.logFile
274 static getLogErrorFile(): string | undefined {
275 Configuration
.warnDeprecatedConfigurationKey(
278 "Use 'logErrorFile' instead"
280 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logErrorFile')
281 ? Configuration
.getConfig()?.logErrorFile
285 static getSupervisionUrls(): string | string[] | undefined {
286 Configuration
.warnDeprecatedConfigurationKey(
289 "Use 'supervisionUrls' instead"
291 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
292 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
296 return Configuration
.getConfig()?.supervisionUrls
;
299 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
300 Configuration
.warnDeprecatedConfigurationKey(
301 'distributeStationToTenantEqually',
303 "Use 'supervisionUrlDistribution' instead"
305 Configuration
.warnDeprecatedConfigurationKey(
306 'distributeStationsToTenantsEqually',
308 "Use 'supervisionUrlDistribution' instead"
310 return Utils
.hasOwnProp(Configuration
.getConfig(), 'supervisionUrlDistribution')
311 ? Configuration
.getConfig()?.supervisionUrlDistribution
312 : SupervisionUrlDistribution
.ROUND_ROBIN
;
315 private static logPrefix
= (): string => {
316 return `${new Date().toLocaleString()} Simulator configuration |`;
319 private static warnDeprecatedConfigurationKey(
321 sectionName
?: string,
326 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
327 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
330 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
331 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}
` : ''
334 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
336 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
337 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}
` : ''
343 // Read the config file
344 private static getConfig(): ConfigurationData
| null {
345 if (!Configuration
.configuration
) {
347 Configuration
.configuration
= JSON
.parse(
348 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
349 ) as ConfigurationData
;
351 Configuration
.handleFileException(
352 Configuration
.configurationFile
,
353 FileType
.Configuration
,
354 error
as NodeJS
.ErrnoException
,
355 Configuration
.logPrefix()
358 if (!Configuration
.configurationFileWatcher
) {
359 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
362 return Configuration
.configuration
;
365 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
367 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
368 if (filename
?.trim().length
> 0 && event
=== 'change') {
369 // Nullify to force configuration file reading
370 Configuration
.configuration
= null;
371 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
372 Configuration
.configurationChangeCallback().catch((error
) => {
373 throw typeof error
=== 'string' ? new Error(error
) : error
;
379 Configuration
.handleFileException(
380 Configuration
.configurationFile
,
381 FileType
.Configuration
,
382 error
as NodeJS
.ErrnoException
,
383 Configuration
.logPrefix()
388 private static handleFileException(
391 error
: NodeJS
.ErrnoException
,
394 const prefix
= Utils
.isNotEmptyString(logPrefix
) ? `${logPrefix} ` : '';
396 switch (error
.code
) {
398 logMsg
= `${fileType} file ${file} not found:`;
401 logMsg
= `${fileType} file ${file} already exists:`;
404 logMsg
= `${fileType} file ${file} access denied:`;
407 logMsg
= `${fileType} file ${file} error:`;
409 console
.warn(`${chalk.green(prefix)}${chalk.yellow(`${logMsg} `)}`, error);
412 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
413 switch (storageType) {
414 case StorageType.JSON_FILE:
415 return Configuration.buildPerformanceUriFilePath(
416 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
418 case StorageType.SQLITE:
419 return Configuration.buildPerformanceUriFilePath(
420 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
423 throw new Error(`Performance storage URI
is mandatory
with storage
type '${storageType}'`);
427 private static buildPerformanceUriFilePath(file: string) {
428 return `file
://${path.join(
429 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),