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';
10 import { Constants
} from
'./Constants';
11 import { FileUtils
} from
'./FileUtils';
12 import { Utils
} from
'./Utils';
15 type ConfigurationData
,
17 type StationTemplateUrl
,
18 type StorageConfiguration
,
20 SupervisionUrlDistribution
,
21 type UIServerConfiguration
,
22 type WorkerConfiguration
,
24 import { WorkerConstants
, WorkerProcessType
} from
'../worker';
26 export class Configuration
{
27 private static configurationFile
= path
.join(
28 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
33 private static configurationFileWatcher
: fs
.FSWatcher
| undefined;
34 private static configuration
: ConfigurationData
| null = null;
35 private static configurationChangeCallback
: () => Promise
<void>;
37 private constructor() {
38 // This is intentional
41 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
42 Configuration
.configurationChangeCallback
= cb
;
45 static getLogStatisticsInterval(): number | undefined {
46 Configuration
.warnDeprecatedConfigurationKey(
47 'statisticsDisplayInterval',
49 "Use 'logStatisticsInterval' instead"
52 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logStatisticsInterval')
53 ? Configuration
.getConfig()?.logStatisticsInterval
54 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
57 static getUIServer(): UIServerConfiguration
{
58 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiWebSocketServer')) {
60 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
63 let uiServerConfiguration
: UIServerConfiguration
= {
65 type: ApplicationProtocol
.WS
,
67 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
68 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
71 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'uiServer')) {
72 uiServerConfiguration
= merge
<UIServerConfiguration
>(
73 uiServerConfiguration
,
74 Configuration
.getConfig()?.uiServer
77 if (Utils
.isCFEnvironment() === true) {
78 delete uiServerConfiguration
.options
?.host
;
79 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
81 return uiServerConfiguration
;
84 static getPerformanceStorage(): StorageConfiguration
{
85 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
86 let storageConfiguration
: StorageConfiguration
= {
88 type: StorageType
.JSON_FILE
,
89 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
91 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'performanceStorage')) {
92 storageConfiguration
= {
93 ...storageConfiguration
,
94 ...Configuration
.getConfig()?.performanceStorage
,
95 ...(Configuration
.getConfig()?.performanceStorage
?.type === StorageType
.JSON_FILE
&&
96 Configuration
.getConfig()?.performanceStorage
?.uri
&& {
97 uri
: `file://${path.join(
98 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
99 new URL(Configuration.getConfig()?.performanceStorage?.uri).pathname
104 return storageConfiguration
;
107 static getAutoReconnectMaxRetries(): number | undefined {
108 Configuration
.warnDeprecatedConfigurationKey(
109 'autoReconnectTimeout',
111 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
113 Configuration
.warnDeprecatedConfigurationKey(
116 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
118 Configuration
.warnDeprecatedConfigurationKey(
119 'autoReconnectMaxRetries',
121 'Use it in charging station template instead'
124 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
125 return Configuration
.getConfig()?.autoReconnectMaxRetries
;
129 static getStationTemplateUrls(): StationTemplateUrl
[] | undefined {
130 Configuration
.warnDeprecatedConfigurationKey(
131 'stationTemplateURLs',
133 "Use 'stationTemplateUrls' instead"
135 !Utils
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
136 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
137 'stationTemplateURLs'
138 ] as StationTemplateUrl
[]);
139 Configuration
.getConfig().stationTemplateUrls
.forEach(
140 (stationTemplateUrl
: StationTemplateUrl
) => {
141 if (!Utils
.isUndefined(stationTemplateUrl
['numberOfStation'])) {
143 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
144 stationTemplateUrl.file
145 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
151 return Configuration
.getConfig()?.stationTemplateUrls
;
154 static getWorker(): WorkerConfiguration
{
155 Configuration
.warnDeprecatedConfigurationKey(
158 "Use 'worker' section to define the type of worker process model instead"
160 Configuration
.warnDeprecatedConfigurationKey(
163 "Use 'worker' section to define the type of worker process model instead"
165 Configuration
.warnDeprecatedConfigurationKey(
168 "Use 'worker' section to define the worker start delay instead"
170 Configuration
.warnDeprecatedConfigurationKey(
171 'chargingStationsPerWorker',
173 "Use 'worker' section to define the number of element(s) per worker instead"
175 Configuration
.warnDeprecatedConfigurationKey(
178 "Use 'worker' section to define the worker's element start delay instead"
180 Configuration
.warnDeprecatedConfigurationKey(
183 "Use 'worker' section to define the worker pool minimum size instead"
185 Configuration
.warnDeprecatedConfigurationKey(
188 "Use 'worker' section to define the worker pool maximum size instead"
190 Configuration
.warnDeprecatedConfigurationKey(
191 'workerPoolMaxSize;',
193 "Use 'worker' section to define the worker pool maximum size instead"
195 Configuration
.warnDeprecatedConfigurationKey(
196 'workerPoolStrategy;',
198 "Use 'worker' section to define the worker pool strategy instead"
200 let workerConfiguration
: WorkerConfiguration
= {
201 processType
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerProcess')
202 ? Configuration
.getConfig()?.workerProcess
203 : WorkerProcessType
.workerSet
,
204 startDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerStartDelay')
205 ? Configuration
.getConfig()?.workerStartDelay
206 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
207 elementsPerWorker
: Utils
.hasOwnProp(Configuration
.getConfig(), 'chargingStationsPerWorker')
208 ? Configuration
.getConfig()?.chargingStationsPerWorker
209 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
210 elementStartDelay
: Utils
.hasOwnProp(Configuration
.getConfig(), 'elementStartDelay')
211 ? Configuration
.getConfig()?.elementStartDelay
212 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
213 poolMinSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMinSize')
214 ? Configuration
.getConfig()?.workerPoolMinSize
215 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
216 poolMaxSize
: Utils
.hasOwnProp(Configuration
.getConfig(), 'workerPoolMaxSize')
217 ? Configuration
.getConfig()?.workerPoolMaxSize
218 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
220 Configuration
.getConfig()?.workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
222 if (Utils
.hasOwnProp(Configuration
.getConfig(), 'worker')) {
223 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig()?.worker
};
225 return workerConfiguration
;
228 static getLogConsole(): boolean | undefined {
229 Configuration
.warnDeprecatedConfigurationKey(
232 "Use 'logConsole' instead"
234 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logConsole')
235 ? Configuration
.getConfig()?.logConsole
239 static getLogFormat(): string | undefined {
240 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFormat')
241 ? Configuration
.getConfig()?.logFormat
245 static getLogRotate(): boolean | undefined {
246 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logRotate')
247 ? Configuration
.getConfig()?.logRotate
251 static getLogMaxFiles(): number | string | false | undefined {
253 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
254 Configuration
.getConfig()?.logMaxFiles
258 static getLogMaxSize(): number | string | false | undefined {
260 Utils
.hasOwnProp(Configuration
.getConfig(), 'logMaxFiles') &&
261 Configuration
.getConfig()?.logMaxSize
265 static getLogLevel(): string | undefined {
266 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logLevel')
267 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
271 static getLogFile(): string | undefined {
272 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logFile')
273 ? Configuration
.getConfig()?.logFile
277 static getLogErrorFile(): string | undefined {
278 Configuration
.warnDeprecatedConfigurationKey(
281 "Use 'logErrorFile' instead"
283 return Utils
.hasOwnProp(Configuration
.getConfig(), 'logErrorFile')
284 ? Configuration
.getConfig()?.logErrorFile
288 static getSupervisionUrls(): string | string[] | undefined {
289 Configuration
.warnDeprecatedConfigurationKey(
292 "Use 'supervisionUrls' instead"
294 !Utils
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
295 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
299 return Configuration
.getConfig()?.supervisionUrls
;
302 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
303 Configuration
.warnDeprecatedConfigurationKey(
304 'distributeStationToTenantEqually',
306 "Use 'supervisionUrlDistribution' instead"
308 Configuration
.warnDeprecatedConfigurationKey(
309 'distributeStationsToTenantsEqually',
311 "Use 'supervisionUrlDistribution' instead"
313 return Utils
.hasOwnProp(Configuration
.getConfig(), 'supervisionUrlDistribution')
314 ? Configuration
.getConfig()?.supervisionUrlDistribution
315 : SupervisionUrlDistribution
.ROUND_ROBIN
;
318 private static logPrefix
= (): string => {
319 return `${new Date().toLocaleString()} Simulator configuration |`;
322 private static warnDeprecatedConfigurationKey(
324 sectionName
?: string,
329 !Utils
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
330 !Utils
.isUndefined((Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
])
333 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
334 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}
` : ''
337 } else if (!Utils
.isUndefined(Configuration
.getConfig()[key
])) {
339 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
340 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}
` : ''
346 // Read the config file
347 private static getConfig(): ConfigurationData
| null {
348 if (!Configuration
.configuration
) {
350 Configuration
.configuration
= JSON
.parse(
351 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
352 ) as ConfigurationData
;
354 FileUtils
.handleFileException(
355 Configuration
.configurationFile
,
356 FileType
.Configuration
,
357 error
as NodeJS
.ErrnoException
,
358 Configuration
.logPrefix(),
362 if (!Configuration
.configurationFileWatcher
) {
363 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
366 return Configuration
.configuration
;
369 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
371 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
372 if (filename
?.trim().length
> 0 && event
=== 'change') {
373 // Nullify to force configuration file reading
374 Configuration
.configuration
= null;
375 if (!Utils
.isUndefined(Configuration
.configurationChangeCallback
)) {
376 Configuration
.configurationChangeCallback().catch((error
) => {
377 throw typeof error
=== 'string' ? new Error(error
) : error
;
383 FileUtils
.handleFileException(
384 Configuration
.configurationFile
,
385 FileType
.Configuration
,
386 error
as NodeJS
.ErrnoException
,
387 Configuration
.logPrefix(),
393 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
394 switch (storageType
) {
395 case StorageType
.JSON_FILE
:
396 return `file://${path.join(
397 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
398 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
400 case StorageType
.SQLITE
:
401 return `file://${path.join(
402 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
403 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
406 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);