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()}} {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 public 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 public 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 public 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 public 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 public static workerPoolInUse(): boolean {
226 return [WorkerProcessType
.dynamicPool
, WorkerProcessType
.staticPool
].includes(
227 Configuration
.getWorker().processType
231 public static workerDynamicPoolInUse(): boolean {
232 return Configuration
.getWorker().processType
=== WorkerProcessType
.dynamicPool
;
235 public static getLogConsole(): boolean | undefined {
236 Configuration
.warnDeprecatedConfigurationKey(
239 "Use 'logConsole' instead"
241 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logConsole')
242 ? Configuration
.getConfig()?.logConsole
246 public static getLogFormat(): string | undefined {
247 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFormat')
248 ? Configuration
.getConfig()?.logFormat
252 public static getLogRotate(): boolean | undefined {
253 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logRotate')
254 ? Configuration
.getConfig()?.logRotate
258 public static getLogMaxFiles(): number | string | false | undefined {
260 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
261 Configuration
.getConfig()?.logMaxFiles
265 public static getLogMaxSize(): number | string | false | undefined {
267 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
268 Configuration
.getConfig()?.logMaxSize
272 public static getLogLevel(): string | undefined {
273 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logLevel')
274 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
278 public static getLogFile(): string | undefined {
279 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFile')
280 ? Configuration
.getConfig()?.logFile
284 public static getLogErrorFile(): string | undefined {
285 Configuration
.warnDeprecatedConfigurationKey(
288 "Use 'logErrorFile' instead"
290 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logErrorFile')
291 ? Configuration
.getConfig()?.logErrorFile
295 public static getSupervisionUrls(): string | string[] | undefined {
296 Configuration
.warnDeprecatedConfigurationKey(
299 "Use 'supervisionUrls' instead"
301 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
302 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
306 return Configuration
.getConfig()?.supervisionUrls
;
309 public static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
310 Configuration
.warnDeprecatedConfigurationKey(
311 'distributeStationToTenantEqually',
313 "Use 'supervisionUrlDistribution' instead"
315 Configuration
.warnDeprecatedConfigurationKey(
316 'distributeStationsToTenantsEqually',
318 "Use 'supervisionUrlDistribution' instead"
320 return Utils
.hasOwnProp(Configuration
.getConfig(), 'supervisionUrlDistribution')
321 ? Configuration
.getConfig()?.supervisionUrlDistribution
322 : SupervisionUrlDistribution
.ROUND_ROBIN
;
325 private static logPrefix
= (): string => {
326 return `${new Date().toLocaleString()} Simulator configuration |`;
329 private static warnDeprecatedConfigurationKey(
331 sectionName
?: string,
336 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
337 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
340 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
341 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}
` : ''
344 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
346 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
347 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}
` : ''
353 // Read the config file
354 private static getConfig(): ConfigurationData
| null {
355 if (!Configuration
.configuration
) {
357 Configuration
.configuration
= JSON
.parse(
358 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
359 ) as ConfigurationData
;
361 Configuration
.handleFileException(
362 Configuration
.configurationFile
,
363 FileType
.Configuration
,
364 error
as NodeJS
.ErrnoException
,
365 Configuration
.logPrefix()
368 if (!Configuration
.configurationFileWatcher
) {
369 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
372 return Configuration
.configuration
;
375 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
377 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
378 if (filename
?.trim().length
> 0 && event
=== 'change') {
379 // Nullify to force configuration file reading
380 Configuration
.configuration
= null;
381 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
382 Configuration
.configurationChangeCallback().catch((error
) => {
383 throw typeof error
=== 'string' ? new Error(error
) : error
;
389 Configuration
.handleFileException(
390 Configuration
.configurationFile
,
391 FileType
.Configuration
,
392 error
as NodeJS
.ErrnoException
,
393 Configuration
.logPrefix()
398 private static handleFileException(
401 error
: NodeJS
.ErrnoException
,
404 const prefix
= Utils
.isNotEmptyString(logPrefix
) ? `${logPrefix} ` : '';
406 switch (error
.code
) {
408 logMsg
= `${fileType} file ${file} not found:`;
411 logMsg
= `${fileType} file ${file} already exists:`;
414 logMsg
= `${fileType} file ${file} access denied:`;
417 logMsg
= `${fileType} file ${file} permission denied:`;
420 logMsg
= `${fileType} file ${file} error:`;
422 console
.error(`${chalk.green(prefix)}${chalk.red(`${logMsg} `)}`, error);
426 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
427 switch (storageType) {
428 case StorageType.JSON_FILE:
429 return Configuration.buildPerformanceUriFilePath(
430 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
432 case StorageType.SQLITE:
433 return Configuration.buildPerformanceUriFilePath(
434 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
437 throw new Error(`Performance storage URI
is mandatory
with storage
type '${storageType}'`);
441 private static buildPerformanceUriFilePath(file: string) {
442 return `file
://${path.join(
443 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),