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 LogConfiguration
,
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
.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 public static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
41 Configuration
.configurationChangeCallback
= cb
;
44 public static getUIServer(): UIServerConfiguration
{
45 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiWebSocketServer')) {
47 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
48 "Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead"
52 let uiServerConfiguration
: UIServerConfiguration
= {
54 type: ApplicationProtocol
.WS
,
56 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
57 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
60 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiServer')) {
61 uiServerConfiguration
= merge
<UIServerConfiguration
>(
62 uiServerConfiguration
,
63 Configuration
.getConfig()?.uiServer
66 if (Utils
.isCFEnvironment() === true) {
67 delete uiServerConfiguration
.options
?.host
;
68 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
70 return uiServerConfiguration
;
73 public static getPerformanceStorage(): StorageConfiguration
{
74 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
75 let storageConfiguration
: StorageConfiguration
= {
77 type: StorageType
.JSON_FILE
,
78 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
80 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'performanceStorage')) {
81 storageConfiguration
= {
82 ...storageConfiguration
,
83 ...Configuration
.getConfig()?.performanceStorage
,
84 ...(Configuration
.getConfig()?.performanceStorage
?.type === StorageType
.JSON_FILE
&&
85 Configuration
.getConfig()?.performanceStorage
?.uri
&& {
86 uri
: Configuration
.buildPerformanceUriFilePath(
87 new URL(Configuration
.getConfig()?.performanceStorage
?.uri
).pathname
92 return storageConfiguration
;
95 public static getAutoReconnectMaxRetries(): number | undefined {
96 Configuration
.warnDeprecatedConfigurationKey(
97 'autoReconnectTimeout',
99 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
101 Configuration
.warnDeprecatedConfigurationKey(
104 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
106 Configuration
.warnDeprecatedConfigurationKey(
107 'autoReconnectMaxRetries',
109 'Use it in charging station template instead'
112 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
113 return Configuration
.getConfig()?.autoReconnectMaxRetries
;
117 public static getStationTemplateUrls(): StationTemplateUrl
[] | undefined {
118 Configuration
.warnDeprecatedConfigurationKey(
119 'stationTemplateURLs',
121 "Use 'stationTemplateUrls' instead"
123 !Utils
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
124 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
125 'stationTemplateURLs'
126 ] as StationTemplateUrl
[]);
127 Configuration
.getConfig().stationTemplateUrls
.forEach(
128 (stationTemplateUrl
: StationTemplateUrl
) => {
129 if (!Utils
.isUndefined(stationTemplateUrl
['numberOfStation'])) {
131 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
132 `Deprecated configuration key
'numberOfStation' usage
for template file
'${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use
'numberOfStations' instead
`
139 return Configuration
.getConfig()?.stationTemplateUrls
;
142 public static getLog(): LogConfiguration
{
143 Configuration
.warnDeprecatedConfigurationKey(
146 "Use 'log' section to define the logging enablement instead"
148 Configuration
.warnDeprecatedConfigurationKey(
151 "Use 'log' section to define the log file instead"
153 Configuration
.warnDeprecatedConfigurationKey(
156 "Use 'log' section to define the log error file instead"
158 Configuration
.warnDeprecatedConfigurationKey(
161 "Use 'log' section to define the console logging enablement instead"
163 Configuration
.warnDeprecatedConfigurationKey(
164 'logStatisticsInterval',
166 "Use 'log' section to define the log statistics interval instead"
168 Configuration
.warnDeprecatedConfigurationKey(
171 "Use 'log' section to define the log level instead"
173 Configuration
.warnDeprecatedConfigurationKey(
176 "Use 'log' section to define the log format instead"
178 Configuration
.warnDeprecatedConfigurationKey(
181 "Use 'log' section to define the log rotation enablement instead"
183 Configuration
.warnDeprecatedConfigurationKey(
186 "Use 'log' section to define the log maximum files instead"
188 Configuration
.warnDeprecatedConfigurationKey(
191 "Use 'log' section to define the log maximum size instead"
193 const defaultLogConfiguration
: LogConfiguration
= {
195 file
: 'logs/combined.log',
196 errorFile
: 'logs/error.log',
197 statisticsInterval
: Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
,
202 const deprecatedLogConfiguration
: LogConfiguration
= {
203 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logEnabled') && {
204 enabled
: Configuration
.getConfig()?.logEnabled
,
206 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logFile') && {
207 file
: Configuration
.getConfig()?.logFile
,
209 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logErrorFile') && {
210 errorFile
: Configuration
.getConfig()?.logErrorFile
,
212 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logStatisticsInterval') && {
213 statisticsInterval
: Configuration
.getConfig()?.logStatisticsInterval
,
215 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logLevel') && {
216 level
: Configuration
.getConfig()?.logLevel
,
218 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logConsole') && {
219 console
: Configuration
.getConfig()?.logConsole
,
221 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logFormat') && {
222 format
: Configuration
.getConfig()?.logFormat
,
224 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logRotate') && {
225 rotate
: Configuration
.getConfig()?.logRotate
,
227 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') && {
228 maxFiles
: Configuration
.getConfig()?.logMaxFiles
,
230 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxSize') && {
231 maxSize
: Configuration
.getConfig()?.logMaxSize
,
234 const logConfiguration
: LogConfiguration
= {
235 ...defaultLogConfiguration
,
236 ...deprecatedLogConfiguration
,
237 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'log') && Configuration
.getConfig()?.log
),
239 return logConfiguration
;
242 public static getWorker(): WorkerConfiguration
{
243 Configuration
.warnDeprecatedConfigurationKey(
246 "Use 'worker' section to define the type of worker process model instead"
248 Configuration
.warnDeprecatedConfigurationKey(
251 "Use 'worker' section to define the type of worker process model instead"
253 Configuration
.warnDeprecatedConfigurationKey(
256 "Use 'worker' section to define the worker start delay instead"
258 Configuration
.warnDeprecatedConfigurationKey(
259 'chargingStationsPerWorker',
261 "Use 'worker' section to define the number of element(s) per worker instead"
263 Configuration
.warnDeprecatedConfigurationKey(
266 "Use 'worker' section to define the worker's element start delay instead"
268 Configuration
.warnDeprecatedConfigurationKey(
271 "Use 'worker' section to define the worker pool minimum size instead"
273 Configuration
.warnDeprecatedConfigurationKey(
276 "Use 'worker' section to define the worker pool maximum size instead"
278 Configuration
.warnDeprecatedConfigurationKey(
279 'workerPoolMaxSize;',
281 "Use 'worker' section to define the worker pool maximum size instead"
283 Configuration
.warnDeprecatedConfigurationKey(
284 'workerPoolStrategy;',
286 "Use 'worker' section to define the worker pool strategy instead"
288 const defaultWorkerConfiguration
: WorkerConfiguration
= {
289 processType
: WorkerProcessType
.workerSet
,
290 startDelay
: WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
291 elementsPerWorker
: WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
292 elementStartDelay
: WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
293 poolMinSize
: WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
294 poolMaxSize
: WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
295 poolStrategy
: WorkerChoiceStrategies
.ROUND_ROBIN
,
297 const deprecatedWorkerConfiguration
: WorkerConfiguration
= {
298 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'workerProcess') && {
299 processType
: Configuration
.getConfig()?.workerProcess
,
301 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'workerStartDelay') && {
302 startDelay
: Configuration
.getConfig()?.workerStartDelay
,
304 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'chargingStationsPerWorker') && {
305 elementsPerWorker
: Configuration
.getConfig()?.chargingStationsPerWorker
,
307 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'elementStartDelay') && {
308 elementStartDelay
: Configuration
.getConfig()?.elementStartDelay
,
310 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMinSize') && {
311 poolMinSize
: Configuration
.getConfig()?.workerPoolMinSize
,
313 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMaxSize') && {
314 poolMaxSize
: Configuration
.getConfig()?.workerPoolMaxSize
,
316 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolStrategy') && {
318 Configuration
.getConfig()?.workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
321 const workerConfiguration
: WorkerConfiguration
= {
322 ...defaultWorkerConfiguration
,
323 ...deprecatedWorkerConfiguration
,
324 ...(Utils
.hasOwnProp(Configuration
.getConfig(), 'worker') &&
325 Configuration
.getConfig()?.worker
),
327 return workerConfiguration
;
330 public static workerPoolInUse(): boolean {
331 return [WorkerProcessType
.dynamicPool
, WorkerProcessType
.staticPool
].includes(
332 Configuration
.getWorker().processType
336 public static workerDynamicPoolInUse(): boolean {
337 return Configuration
.getWorker().processType
=== WorkerProcessType
.dynamicPool
;
340 public static getSupervisionUrls(): string | string[] | undefined {
341 Configuration
.warnDeprecatedConfigurationKey(
344 "Use 'supervisionUrls' instead"
346 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
347 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
351 return Configuration
.getConfig()?.supervisionUrls
;
354 public static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
355 Configuration
.warnDeprecatedConfigurationKey(
356 'distributeStationToTenantEqually',
358 "Use 'supervisionUrlDistribution' instead"
360 Configuration
.warnDeprecatedConfigurationKey(
361 'distributeStationsToTenantsEqually',
363 "Use 'supervisionUrlDistribution' instead"
365 return Utils
.hasOwnProp(Configuration
.getConfig(), 'supervisionUrlDistribution')
366 ? Configuration
.getConfig()?.supervisionUrlDistribution
367 : SupervisionUrlDistribution
.ROUND_ROBIN
;
370 private static logPrefix
= (): string => {
371 return `${new Date().toLocaleString()} Simulator configuration |`;
374 private static warnDeprecatedConfigurationKey(
376 sectionName
?: string,
381 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
382 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
385 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
386 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
387 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
391 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
393 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
394 `Deprecated configuration key
'${key}' usage$
{
395 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
402 // Read the config file
403 private static getConfig(): ConfigurationData
| null {
404 if (!Configuration
.configuration
) {
406 Configuration
.configuration
= JSON
.parse(
407 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
408 ) as ConfigurationData
;
410 Configuration
.handleFileException(
411 Configuration
.configurationFile
,
412 FileType
.Configuration
,
413 error
as NodeJS
.ErrnoException
,
414 Configuration
.logPrefix()
417 if (!Configuration
.configurationFileWatcher
) {
418 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
421 return Configuration
.configuration
;
424 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
426 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
427 if (filename
?.trim().length
> 0 && event
=== 'change') {
428 // Nullify to force configuration file reading
429 Configuration
.configuration
= null;
430 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
431 Configuration
.configurationChangeCallback().catch((error
) => {
432 throw typeof error
=== 'string' ? new Error(error
) : error
;
438 Configuration
.handleFileException(
439 Configuration
.configurationFile
,
440 FileType
.Configuration
,
441 error
as NodeJS
.ErrnoException
,
442 Configuration
.logPrefix()
447 private static handleFileException(
450 error
: NodeJS
.ErrnoException
,
453 const prefix
= Utils
.isNotEmptyString(logPrefix
) ? `${logPrefix} ` : '';
455 switch (error
.code
) {
457 logMsg
= `${fileType} file ${file} not found:`;
460 logMsg
= `${fileType} file ${file} already exists:`;
463 logMsg
= `${fileType} file ${file} access denied:`;
466 logMsg
= `${fileType} file ${file} permission denied:`;
469 logMsg
= `${fileType} file ${file} error:`;
471 console
.error(`${chalk.green(prefix)}${chalk.red(`${logMsg} `)}`, error);
475 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
476 switch (storageType) {
477 case StorageType.JSON_FILE:
478 return Configuration.buildPerformanceUriFilePath(
479 `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}
/${Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME}
`
481 case StorageType.SQLITE:
482 return Configuration.buildPerformanceUriFilePath(
483 `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}
/${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
486 throw new Error(`Performance storage URI
is mandatory
with storage
type '${storageType}'`);
490 private static buildPerformanceUriFilePath(file: string) {
491 return `file
://${path.join(
492 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),