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 { FileUtils
} from
'./FileUtils';
11 import { Utils
} from
'./Utils';
14 type ConfigurationData
,
16 type StationTemplateUrl
,
17 type StorageConfiguration
,
19 SupervisionUrlDistribution
,
20 type UIServerConfiguration
,
21 type WorkerConfiguration
,
23 import { WorkerConstants
, WorkerProcessType
} from
'../worker';
25 export class Configuration
{
26 private static configurationFile
= path
.join(
27 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
32 private static configurationFileWatcher
: fs
.FSWatcher
| undefined;
33 private static configuration
: ConfigurationData
| null = null;
34 private static configurationChangeCallback
: () => Promise
<void>;
36 private constructor() {
37 // This is intentional
40 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
41 Configuration
.configurationChangeCallback
= cb
;
44 static getLogStatisticsInterval(): number | undefined {
45 Configuration
.warnDeprecatedConfigurationKey(
46 'statisticsDisplayInterval',
48 "Use 'logStatisticsInterval' instead"
51 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logStatisticsInterval')
52 ? Configuration
.getConfig()?.logStatisticsInterval
53 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
56 static getUIServer(): UIServerConfiguration
{
57 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiWebSocketServer')) {
59 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
62 let uiServerConfiguration
: UIServerConfiguration
= {
64 type: ApplicationProtocol
.WS
,
66 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
67 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
70 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiServer')) {
71 uiServerConfiguration
= merge
<UIServerConfiguration
>(
72 uiServerConfiguration
,
73 Configuration
.getConfig()?.uiServer
76 if (Utils
.isCFEnvironment() === true) {
77 delete uiServerConfiguration
.options
?.host
;
78 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
80 return uiServerConfiguration
;
83 static getPerformanceStorage(): StorageConfiguration
{
84 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
85 let storageConfiguration
: StorageConfiguration
= {
87 type: StorageType
.JSON_FILE
,
88 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
90 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'performanceStorage')) {
91 storageConfiguration
= {
92 ...storageConfiguration
,
93 ...Configuration
.getConfig()?.performanceStorage
,
94 ...(Configuration
.getConfig()?.performanceStorage
?.type === StorageType
.JSON_FILE
&&
95 Configuration
.getConfig()?.performanceStorage
?.uri
&& {
96 uri
: Configuration
.buildPerformanceUriFilePath(
97 new URL(Configuration
.getConfig()?.performanceStorage
?.uri
).pathname
102 return storageConfiguration
;
105 static getAutoReconnectMaxRetries(): number | undefined {
106 Configuration
.warnDeprecatedConfigurationKey(
107 'autoReconnectTimeout',
109 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
111 Configuration
.warnDeprecatedConfigurationKey(
114 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
116 Configuration
.warnDeprecatedConfigurationKey(
117 'autoReconnectMaxRetries',
119 'Use it in charging station template instead'
122 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
123 return Configuration
.getConfig()?.autoReconnectMaxRetries
;
127 static getStationTemplateUrls(): StationTemplateUrl
[] | undefined {
128 Configuration
.warnDeprecatedConfigurationKey(
129 'stationTemplateURLs',
131 "Use 'stationTemplateUrls' instead"
133 !Utils
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
134 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
135 'stationTemplateURLs'
136 ] as StationTemplateUrl
[]);
137 Configuration
.getConfig().stationTemplateUrls
.forEach(
138 (stationTemplateUrl
: StationTemplateUrl
) => {
139 if (!Utils
.isUndefined(stationTemplateUrl
['numberOfStation'])) {
141 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
142 stationTemplateUrl.file
143 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
149 return Configuration
.getConfig()?.stationTemplateUrls
;
152 static getWorker(): WorkerConfiguration
{
153 Configuration
.warnDeprecatedConfigurationKey(
156 "Use 'worker' section to define the type of worker process model instead"
158 Configuration
.warnDeprecatedConfigurationKey(
161 "Use 'worker' section to define the type of worker process model instead"
163 Configuration
.warnDeprecatedConfigurationKey(
166 "Use 'worker' section to define the worker start delay instead"
168 Configuration
.warnDeprecatedConfigurationKey(
169 'chargingStationsPerWorker',
171 "Use 'worker' section to define the number of element(s) per worker instead"
173 Configuration
.warnDeprecatedConfigurationKey(
176 "Use 'worker' section to define the worker's element start delay instead"
178 Configuration
.warnDeprecatedConfigurationKey(
181 "Use 'worker' section to define the worker pool minimum size instead"
183 Configuration
.warnDeprecatedConfigurationKey(
186 "Use 'worker' section to define the worker pool maximum size instead"
188 Configuration
.warnDeprecatedConfigurationKey(
189 'workerPoolMaxSize;',
191 "Use 'worker' section to define the worker pool maximum size instead"
193 Configuration
.warnDeprecatedConfigurationKey(
194 'workerPoolStrategy;',
196 "Use 'worker' section to define the worker pool strategy instead"
198 let workerConfiguration
: WorkerConfiguration
= {
199 processType
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerProcess')
200 ? Configuration
.getConfig()?.workerProcess
201 : WorkerProcessType
.workerSet
,
202 startDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerStartDelay')
203 ? Configuration
.getConfig()?.workerStartDelay
204 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
205 elementsPerWorker
: Utils
.hasOwnProp(Configuration
.getConfig(), 'chargingStationsPerWorker')
206 ? Configuration
.getConfig()?.chargingStationsPerWorker
207 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
208 elementStartDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'elementStartDelay')
209 ? Configuration
.getConfig()?.elementStartDelay
210 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
211 poolMinSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMinSize')
212 ? Configuration
.getConfig()?.workerPoolMinSize
213 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
214 poolMaxSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMaxSize')
215 ? Configuration
.getConfig()?.workerPoolMaxSize
216 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
218 Configuration
.getConfig()?.workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
220 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'worker')) {
221 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig()?.worker
};
223 return workerConfiguration
;
226 static getLogConsole(): boolean | undefined {
227 Configuration
.warnDeprecatedConfigurationKey(
230 "Use 'logConsole' instead"
232 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logConsole')
233 ? Configuration
.getConfig()?.logConsole
237 static getLogFormat(): string | undefined {
238 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFormat')
239 ? Configuration
.getConfig()?.logFormat
243 static getLogRotate(): boolean | undefined {
244 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logRotate')
245 ? Configuration
.getConfig()?.logRotate
249 static getLogMaxFiles(): number | string | false | undefined {
251 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
252 Configuration
.getConfig()?.logMaxFiles
256 static getLogMaxSize(): number | string | false | undefined {
258 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
259 Configuration
.getConfig()?.logMaxSize
263 static getLogLevel(): string | undefined {
264 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logLevel')
265 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
269 static getLogFile(): string | undefined {
270 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFile')
271 ? Configuration
.getConfig()?.logFile
275 static getLogErrorFile(): string | undefined {
276 Configuration
.warnDeprecatedConfigurationKey(
279 "Use 'logErrorFile' instead"
281 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logErrorFile')
282 ? Configuration
.getConfig()?.logErrorFile
286 static getSupervisionUrls(): string | string[] | undefined {
287 Configuration
.warnDeprecatedConfigurationKey(
290 "Use 'supervisionUrls' instead"
292 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
293 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
297 return Configuration
.getConfig()?.supervisionUrls
;
300 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
301 Configuration
.warnDeprecatedConfigurationKey(
302 'distributeStationToTenantEqually',
304 "Use 'supervisionUrlDistribution' instead"
306 Configuration
.warnDeprecatedConfigurationKey(
307 'distributeStationsToTenantsEqually',
309 "Use 'supervisionUrlDistribution' instead"
311 return Utils
.hasOwnProp(Configuration
.getConfig(), 'supervisionUrlDistribution')
312 ? Configuration
.getConfig()?.supervisionUrlDistribution
313 : SupervisionUrlDistribution
.ROUND_ROBIN
;
316 private static logPrefix
= (): string => {
317 return `${new Date().toLocaleString()} Simulator configuration |`;
320 private static warnDeprecatedConfigurationKey(
322 sectionName
?: string,
327 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
328 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
331 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
332 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}
` : ''
335 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
337 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
338 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}
` : ''
344 // Read the config file
345 private static getConfig(): ConfigurationData
| null {
346 if (!Configuration
.configuration
) {
348 Configuration
.configuration
= JSON
.parse(
349 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
350 ) as ConfigurationData
;
352 FileUtils
.handleFileException(
353 Configuration
.configurationFile
,
354 FileType
.Configuration
,
355 error
as NodeJS
.ErrnoException
,
356 Configuration
.logPrefix(),
360 if (!Configuration
.configurationFileWatcher
) {
361 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
364 return Configuration
.configuration
;
367 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
369 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
370 if (filename
?.trim().length
> 0 && event
=== 'change') {
371 // Nullify to force configuration file reading
372 Configuration
.configuration
= null;
373 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
374 Configuration
.configurationChangeCallback().catch((error
) => {
375 throw typeof error
=== 'string' ? new Error(error
) : error
;
381 FileUtils
.handleFileException(
382 Configuration
.configurationFile
,
383 FileType
.Configuration
,
384 error
as NodeJS
.ErrnoException
,
385 Configuration
.logPrefix(),
391 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
392 switch (storageType
) {
393 case StorageType
.JSON_FILE
:
394 return Configuration
.buildPerformanceUriFilePath(
395 Constants
.DEFAULT_PERFORMANCE_RECORDS_FILENAME
397 case StorageType
.SQLITE
:
398 return Configuration
.buildPerformanceUriFilePath(
399 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`
402 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
406 private static buildPerformanceUriFilePath(file
: string) {
407 return `file://${path.join(
408 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),