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
.hasOwnProp(Configuration
.getConfig(), 'logStatisticsInterval')
50 ? Configuration
.getConfig()?.logStatisticsInterval
51 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
54 static getUIServer(): UIServerConfiguration
{
55 if (Utils
.hasOwnProp(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
.hasOwnProp(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
.hasOwnProp(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
.hasOwnProp(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
.hasOwnProp(Configuration
.getConfig(), 'workerProcess')
190 ? Configuration
.getConfig()?.workerProcess
191 : WorkerProcessType
.WORKER_SET
,
192 startDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerStartDelay')
193 ? Configuration
.getConfig()?.workerStartDelay
194 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
195 elementsPerWorker
: Utils
.hasOwnProp(Configuration
.getConfig(), 'chargingStationsPerWorker')
196 ? Configuration
.getConfig()?.chargingStationsPerWorker
197 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
198 elementStartDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'elementStartDelay')
199 ? Configuration
.getConfig()?.elementStartDelay
200 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
201 poolMinSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMinSize')
202 ? Configuration
.getConfig()?.workerPoolMinSize
203 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
204 poolMaxSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMaxSize')
205 ? Configuration
.getConfig()?.workerPoolMaxSize
206 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
208 Configuration
.getConfig()?.workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
210 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'worker')) {
211 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig()?.worker
};
213 return workerConfiguration
;
216 static getLogConsole(): boolean | undefined {
217 Configuration
.warnDeprecatedConfigurationKey(
220 "Use 'logConsole' instead"
222 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logConsole')
223 ? Configuration
.getConfig()?.logConsole
227 static getLogFormat(): string | undefined {
228 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFormat')
229 ? Configuration
.getConfig()?.logFormat
233 static getLogRotate(): boolean | undefined {
234 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logRotate')
235 ? Configuration
.getConfig()?.logRotate
239 static getLogMaxFiles(): number | string | false | undefined {
241 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
242 Configuration
.getConfig()?.logMaxFiles
246 static getLogMaxSize(): number | string | false | undefined {
248 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
249 Configuration
.getConfig()?.logMaxSize
253 static getLogLevel(): string | undefined {
254 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logLevel')
255 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
259 static getLogFile(): string | undefined {
260 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFile')
261 ? Configuration
.getConfig()?.logFile
265 static getLogErrorFile(): string | undefined {
266 Configuration
.warnDeprecatedConfigurationKey(
269 "Use 'logErrorFile' instead"
271 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logErrorFile')
272 ? Configuration
.getConfig()?.logErrorFile
276 static getSupervisionUrls(): string | string[] | undefined {
277 Configuration
.warnDeprecatedConfigurationKey(
280 "Use 'supervisionUrls' instead"
282 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
283 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
287 return Configuration
.getConfig()?.supervisionUrls
;
290 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
291 Configuration
.warnDeprecatedConfigurationKey(
292 'distributeStationToTenantEqually',
294 "Use 'supervisionUrlDistribution' instead"
296 Configuration
.warnDeprecatedConfigurationKey(
297 'distributeStationsToTenantsEqually',
299 "Use 'supervisionUrlDistribution' instead"
301 return Utils
.hasOwnProp(Configuration
.getConfig(), 'supervisionUrlDistribution')
302 ? Configuration
.getConfig()?.supervisionUrlDistribution
303 : SupervisionUrlDistribution
.ROUND_ROBIN
;
306 private static logPrefix
= (): string => {
307 return `${new Date().toLocaleString()} Simulator configuration |`;
310 private static warnDeprecatedConfigurationKey(
312 sectionName
?: string,
317 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
318 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
321 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
322 logMsgToAppend.trim().length > 0 && `. ${logMsgToAppend}
`
325 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
327 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
328 logMsgToAppend.trim().length > 0 && `. ${logMsgToAppend}
`
334 // Read the config file
335 private static getConfig(): ConfigurationData
| null {
336 if (!Configuration
.configuration
) {
338 Configuration
.configuration
= JSON
.parse(
339 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
340 ) as ConfigurationData
;
342 FileUtils
.handleFileException(
343 Configuration
.configurationFile
,
344 FileType
.Configuration
,
345 error
as NodeJS
.ErrnoException
,
346 Configuration
.logPrefix(),
350 if (!Configuration
.configurationFileWatcher
) {
351 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
354 return Configuration
.configuration
;
357 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
359 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
360 if (filename
?.trim().length
> 0 && event
=== 'change') {
361 // Nullify to force configuration file reading
362 Configuration
.configuration
= null;
363 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
364 Configuration
.configurationChangeCallback().catch((error
) => {
365 throw typeof error
=== 'string' ? new Error(error
) : error
;
371 FileUtils
.handleFileException(
372 Configuration
.configurationFile
,
373 FileType
.Configuration
,
374 error
as NodeJS
.ErrnoException
,
375 Configuration
.logPrefix(),
381 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
382 switch (storageType
) {
383 case StorageType
.JSON_FILE
:
384 return `file://${path.join(
385 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
386 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
388 case StorageType
.SQLITE
:
389 return `file://${path.join(
390 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
391 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
394 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);