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
, FileUtils
, Utils
} from
'./internal';
12 type ConfigurationData
,
14 type StationTemplateUrl
,
15 type StorageConfiguration
,
17 SupervisionUrlDistribution
,
18 type UIServerConfiguration
,
19 type WorkerConfiguration
,
21 import { WorkerConstants
, WorkerProcessType
} from
'../worker';
23 export class Configuration
{
24 private static configurationFile
= path
.join(
25 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
30 private static configurationFileWatcher
: fs
.FSWatcher
| undefined;
31 private static configuration
: ConfigurationData
| null = null;
32 private static configurationChangeCallback
: () => Promise
<void>;
34 private constructor() {
35 // This is intentional
38 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
39 Configuration
.configurationChangeCallback
= cb
;
42 static getLogStatisticsInterval(): number | undefined {
43 Configuration
.warnDeprecatedConfigurationKey(
44 'statisticsDisplayInterval',
46 "Use 'logStatisticsInterval' instead"
49 return Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
50 ? Configuration
.getConfig()?.logStatisticsInterval
51 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
54 static getUIServer(): UIServerConfiguration
{
55 if (Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
57 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
60 let uiServerConfiguration
: UIServerConfiguration
= {
62 type: ApplicationProtocol
.WS
,
64 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
65 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
68 if (Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'uiServer')) {
69 uiServerConfiguration
= merge
<UIServerConfiguration
>(
70 uiServerConfiguration
,
71 Configuration
.getConfig()?.uiServer
74 if (Utils
.isCFEnvironment() === true) {
75 delete uiServerConfiguration
.options
?.host
;
76 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
78 return uiServerConfiguration
;
81 static getPerformanceStorage(): StorageConfiguration
{
82 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
83 let storageConfiguration
: StorageConfiguration
= {
85 type: StorageType
.JSON_FILE
,
86 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
88 if (Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
89 storageConfiguration
= {
90 ...storageConfiguration
,
91 ...Configuration
.getConfig()?.performanceStorage
,
94 return storageConfiguration
;
97 static getAutoReconnectMaxRetries(): number | undefined {
98 Configuration
.warnDeprecatedConfigurationKey(
99 'autoReconnectTimeout',
101 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
103 Configuration
.warnDeprecatedConfigurationKey(
106 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
108 Configuration
.warnDeprecatedConfigurationKey(
109 'autoReconnectMaxRetries',
111 'Use it in charging station template instead'
114 if (Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
115 return Configuration
.getConfig()?.autoReconnectMaxRetries
;
119 static getStationTemplateUrls(): StationTemplateUrl
[] | undefined {
120 Configuration
.warnDeprecatedConfigurationKey(
121 'stationTemplateURLs',
123 "Use 'stationTemplateUrls' instead"
125 !Utils
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
126 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
127 'stationTemplateURLs'
128 ] as unknown
as StationTemplateUrl
[]);
129 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
130 if (!Utils
.isUndefined(stationUrl
['numberOfStation'])) {
132 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
134 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
139 return Configuration
.getConfig()?.stationTemplateUrls
;
142 static getWorker(): WorkerConfiguration
{
143 Configuration
.warnDeprecatedConfigurationKey(
146 "Use 'worker' section to define the type of worker process model instead"
148 Configuration
.warnDeprecatedConfigurationKey(
151 "Use 'worker' section to define the type of worker process model instead"
153 Configuration
.warnDeprecatedConfigurationKey(
156 "Use 'worker' section to define the worker start delay instead"
158 Configuration
.warnDeprecatedConfigurationKey(
159 'chargingStationsPerWorker',
161 "Use 'worker' section to define the number of element(s) per worker instead"
163 Configuration
.warnDeprecatedConfigurationKey(
166 "Use 'worker' section to define the worker's element start delay instead"
168 Configuration
.warnDeprecatedConfigurationKey(
171 "Use 'worker' section to define the worker pool minimum size instead"
173 Configuration
.warnDeprecatedConfigurationKey(
176 "Use 'worker' section to define the worker pool maximum size instead"
178 Configuration
.warnDeprecatedConfigurationKey(
179 'workerPoolMaxSize;',
181 "Use 'worker' section to define the worker pool maximum size instead"
183 Configuration
.warnDeprecatedConfigurationKey(
184 'workerPoolStrategy;',
186 "Use 'worker' section to define the worker pool strategy instead"
188 let workerConfiguration
: WorkerConfiguration
= {
189 processType
: Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
190 ? Configuration
.getConfig()?.workerProcess
191 : WorkerProcessType
.WORKER_SET
,
192 startDelay
: Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
193 ? Configuration
.getConfig()?.workerStartDelay
194 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
195 elementsPerWorker
: Utils
.objectHasOwnProperty(
196 Configuration
.getConfig(),
197 'chargingStationsPerWorker'
199 ? Configuration
.getConfig()?.chargingStationsPerWorker
200 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
201 elementStartDelay
: Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'elementStartDelay')
202 ? Configuration
.getConfig()?.elementStartDelay
203 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
204 poolMinSize
: Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMinSize')
205 ? Configuration
.getConfig()?.workerPoolMinSize
206 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
207 poolMaxSize
: Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMaxSize')
208 ? Configuration
.getConfig()?.workerPoolMaxSize
209 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
211 Configuration
.getConfig()?.workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
213 if (Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'worker')) {
214 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig()?.worker
};
216 return workerConfiguration
;
219 static getLogConsole(): boolean | undefined {
220 Configuration
.warnDeprecatedConfigurationKey(
223 "Use 'logConsole' instead"
225 return Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
226 ? Configuration
.getConfig()?.logConsole
230 static getLogFormat(): string | undefined {
231 return Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
232 ? Configuration
.getConfig()?.logFormat
236 static getLogRotate(): boolean | undefined {
237 return Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
238 ? Configuration
.getConfig()?.logRotate
242 static getLogMaxFiles(): number | string | false | undefined {
244 Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
245 Configuration
.getConfig()?.logMaxFiles
249 static getLogMaxSize(): number | string | false | undefined {
251 Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
252 Configuration
.getConfig()?.logMaxSize
256 static getLogLevel(): string | undefined {
257 return Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
258 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
262 static getLogFile(): string | undefined {
263 return Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
264 ? Configuration
.getConfig()?.logFile
268 static getLogErrorFile(): string | undefined {
269 Configuration
.warnDeprecatedConfigurationKey(
272 "Use 'logErrorFile' instead"
274 return Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
275 ? Configuration
.getConfig()?.logErrorFile
279 static getSupervisionUrls(): string | string[] | undefined {
280 Configuration
.warnDeprecatedConfigurationKey(
283 "Use 'supervisionUrls' instead"
285 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
286 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
290 return Configuration
.getConfig()?.supervisionUrls
;
293 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
294 Configuration
.warnDeprecatedConfigurationKey(
295 'distributeStationToTenantEqually',
297 "Use 'supervisionUrlDistribution' instead"
299 Configuration
.warnDeprecatedConfigurationKey(
300 'distributeStationsToTenantsEqually',
302 "Use 'supervisionUrlDistribution' instead"
304 return Utils
.objectHasOwnProperty(Configuration
.getConfig(), 'supervisionUrlDistribution')
305 ? Configuration
.getConfig()?.supervisionUrlDistribution
306 : SupervisionUrlDistribution
.ROUND_ROBIN
;
309 private static logPrefix
= (): string => {
310 return `${new Date().toLocaleString()} Simulator configuration |`;
313 private static warnDeprecatedConfigurationKey(
315 sectionName
?: string,
320 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
321 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
324 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
325 logMsgToAppend.trim().length > 0 && `. ${logMsgToAppend}
`
328 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
330 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
331 logMsgToAppend.trim().length > 0 && `. ${logMsgToAppend}
`
337 // Read the config file
338 private static getConfig(): ConfigurationData
| null {
339 if (!Configuration
.configuration
) {
341 Configuration
.configuration
= JSON
.parse(
342 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
343 ) as ConfigurationData
;
345 FileUtils
.handleFileException(
346 Configuration
.configurationFile
,
347 FileType
.Configuration
,
348 error
as NodeJS
.ErrnoException
,
349 Configuration
.logPrefix(),
353 if (!Configuration
.configurationFileWatcher
) {
354 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
357 return Configuration
.configuration
;
360 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
362 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
363 if (filename
?.trim().length
> 0 && event
=== 'change') {
364 // Nullify to force configuration file reading
365 Configuration
.configuration
= null;
366 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
367 Configuration
.configurationChangeCallback().catch((error
) => {
368 throw typeof error
=== 'string' ? new Error(error
) : error
;
374 FileUtils
.handleFileException(
375 Configuration
.configurationFile
,
376 FileType
.Configuration
,
377 error
as NodeJS
.ErrnoException
,
378 Configuration
.logPrefix(),
384 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
385 switch (storageType
) {
386 case StorageType
.JSON_FILE
:
387 return `file://${path.join(
388 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
389 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
391 case StorageType
.SQLITE
:
392 return `file://${path.join(
393 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
394 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
397 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);