1 import ConfigurationData
, {
4 SupervisionUrlDistribution
,
5 UIWebSocketServerConfiguration
,
6 } from
'../types/ConfigurationData';
8 import Constants from
'./Constants';
9 import { EmptyObject
} from
'../types/EmptyObject';
10 import { HandleErrorParams
} from
'../types/Error';
11 import { ServerOptions
} from
'ws';
12 import { StorageType
} from
'../types/Storage';
13 import type { WorkerChoiceStrategy
} from
'poolifier';
14 import WorkerConstants from
'../worker/WorkerConstants';
15 import { WorkerProcessType
} from
'../types/Worker';
16 import chalk from
'chalk';
18 import path from
'path';
20 export default class Configuration
{
21 private static configurationFilePath
= path
.join(
22 path
.resolve(__dirname
, '../'),
27 private static configurationFileWatcher
: fs
.FSWatcher
;
28 private static configuration
: ConfigurationData
| null = null;
29 private static configurationChangeCallback
: () => Promise
<void>;
31 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
32 Configuration
.configurationChangeCallback
= cb
;
35 static getLogStatisticsInterval(): number {
36 Configuration
.warnDeprecatedConfigurationKey(
37 'statisticsDisplayInterval',
39 "Use 'logStatisticsInterval' instead"
42 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
43 ? Configuration
.getConfig().logStatisticsInterval
47 static getUIWebSocketServer(): UIWebSocketServerConfiguration
{
48 let options
: ServerOptions
= {
49 host
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_HOST
,
50 port
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_PORT
,
52 let uiWebSocketServerConfiguration
: UIWebSocketServerConfiguration
= {
56 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
58 Configuration
.objectHasOwnProperty(Configuration
.getConfig().uiWebSocketServer
, 'options')
62 ...(Configuration
.objectHasOwnProperty(
63 Configuration
.getConfig().uiWebSocketServer
.options
,
65 ) && { host
: Configuration
.getConfig().uiWebSocketServer
.options
.host
}),
66 ...(Configuration
.objectHasOwnProperty(
67 Configuration
.getConfig().uiWebSocketServer
.options
,
69 ) && { port
: Configuration
.getConfig().uiWebSocketServer
.options
.port
}),
72 uiWebSocketServerConfiguration
= {
73 ...uiWebSocketServerConfiguration
,
74 ...(Configuration
.objectHasOwnProperty(
75 Configuration
.getConfig().uiWebSocketServer
,
77 ) && { enabled
: Configuration
.getConfig().uiWebSocketServer
.enabled
}),
81 return uiWebSocketServerConfiguration
;
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 (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
92 storageConfiguration
= {
93 ...storageConfiguration
,
94 ...(Configuration
.objectHasOwnProperty(
95 Configuration
.getConfig().performanceStorage
,
97 ) && { enabled
: Configuration
.getConfig().performanceStorage
.enabled
}),
98 ...(Configuration
.objectHasOwnProperty(
99 Configuration
.getConfig().performanceStorage
,
101 ) && { type: Configuration
.getConfig().performanceStorage
.type }),
102 ...(Configuration
.objectHasOwnProperty(
103 Configuration
.getConfig().performanceStorage
,
106 uri
: this.getDefaultPerformanceStorageUri(
107 Configuration
.getConfig()?.performanceStorage
?.type ?? StorageType
.JSON_FILE
112 return storageConfiguration
;
115 static getAutoReconnectMaxRetries(): number {
116 Configuration
.warnDeprecatedConfigurationKey(
117 'autoReconnectTimeout',
119 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
121 Configuration
.warnDeprecatedConfigurationKey(
124 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
126 Configuration
.warnDeprecatedConfigurationKey(
127 'autoReconnectMaxRetries',
129 'Use it in charging station template instead'
132 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
133 return Configuration
.getConfig().autoReconnectMaxRetries
;
137 static getStationTemplateUrls(): StationTemplateUrl
[] {
138 Configuration
.warnDeprecatedConfigurationKey(
139 'stationTemplateURLs',
141 "Use 'stationTemplateUrls' instead"
143 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
144 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
145 'stationTemplateURLs'
146 ] as StationTemplateUrl
[]);
147 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
148 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
150 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
152 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
157 return Configuration
.getConfig().stationTemplateUrls
;
160 static getWorkerProcess(): WorkerProcessType
{
161 Configuration
.warnDeprecatedConfigurationKey(
164 "Use 'workerProcess' to define the type of worker process to use instead"
166 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
167 ? Configuration
.getConfig().workerProcess
168 : WorkerProcessType
.WORKER_SET
;
171 static getWorkerStartDelay(): number {
172 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
173 ? Configuration
.getConfig().workerStartDelay
174 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
;
177 static getElementStartDelay(): number {
178 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'elementStartDelay')
179 ? Configuration
.getConfig().elementStartDelay
180 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
;
183 static getWorkerPoolMinSize(): number {
184 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMinSize')
185 ? Configuration
.getConfig().workerPoolMinSize
186 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
;
189 static getWorkerPoolMaxSize(): number {
190 Configuration
.warnDeprecatedConfigurationKey(
193 "Use 'workerPoolMaxSize' instead"
195 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMaxSize')
196 ? Configuration
.getConfig().workerPoolMaxSize
197 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
;
200 static getWorkerPoolStrategy(): WorkerChoiceStrategy
{
201 return Configuration
.getConfig().workerPoolStrategy
;
204 static getChargingStationsPerWorker(): number {
205 return Configuration
.objectHasOwnProperty(
206 Configuration
.getConfig(),
207 'chargingStationsPerWorker'
209 ? Configuration
.getConfig().chargingStationsPerWorker
210 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
;
213 static getLogConsole(): boolean {
214 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
215 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
216 ? Configuration
.getConfig().logConsole
220 static getLogFormat(): string {
221 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
222 ? Configuration
.getConfig().logFormat
226 static getLogRotate(): boolean {
227 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
228 ? Configuration
.getConfig().logRotate
232 static getLogMaxFiles(): number {
233 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
234 ? Configuration
.getConfig().logMaxFiles
238 static getLogLevel(): string {
239 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
240 ? Configuration
.getConfig().logLevel
.toLowerCase()
244 static getLogFile(): string {
245 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
246 ? Configuration
.getConfig().logFile
250 static getLogErrorFile(): string {
251 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
252 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
253 ? Configuration
.getConfig().logErrorFile
257 static getSupervisionUrls(): string | string[] {
258 Configuration
.warnDeprecatedConfigurationKey(
261 "Use 'supervisionUrls' instead"
263 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
264 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
268 return Configuration
.getConfig().supervisionUrls
;
271 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
272 Configuration
.warnDeprecatedConfigurationKey(
273 'distributeStationToTenantEqually',
275 "Use 'supervisionUrlDistribution' instead"
277 Configuration
.warnDeprecatedConfigurationKey(
278 'distributeStationsToTenantsEqually',
280 "Use 'supervisionUrlDistribution' instead"
282 return Configuration
.objectHasOwnProperty(
283 Configuration
.getConfig(),
284 'supervisionUrlDistribution'
286 ? Configuration
.getConfig().supervisionUrlDistribution
287 : SupervisionUrlDistribution
.ROUND_ROBIN
;
290 private static logPrefix(): string {
291 return new Date().toLocaleString() + ' Simulator configuration |';
294 private static warnDeprecatedConfigurationKey(
296 sectionName
?: string,
299 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
302 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
303 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[
308 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
309 logMsgToAppend && '. ' + logMsgToAppend
312 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
314 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
315 logMsgToAppend && '. ' + logMsgToAppend
321 // Read the config file
322 private static getConfig(): ConfigurationData
{
323 if (!Configuration
.configuration
) {
325 Configuration
.configuration
= JSON
.parse(
326 fs
.readFileSync(Configuration
.configurationFilePath
, 'utf8')
327 ) as ConfigurationData
;
329 Configuration
.handleFileException(
330 Configuration
.logPrefix(),
332 Configuration
.configurationFilePath
,
333 error
as NodeJS
.ErrnoException
336 if (!Configuration
.configurationFileWatcher
) {
337 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
340 return Configuration
.configuration
;
343 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
345 return fs
.watch(Configuration
.configurationFilePath
, (event
, filename
): void => {
346 if (filename
&& event
=== 'change') {
347 // Nullify to force configuration file reading
348 Configuration
.configuration
= null;
349 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
350 Configuration
.configurationChangeCallback().catch((error
) => {
351 throw typeof error
=== 'string' ? new Error(error
) : error
;
357 Configuration
.handleFileException(
358 Configuration
.logPrefix(),
360 Configuration
.configurationFilePath
,
366 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
367 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
368 switch (storageType
) {
369 case StorageType
.JSON_FILE
:
370 return `file://${path.join(
371 path.resolve(__dirname, '../../'),
372 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
374 case StorageType
.SQLITE
:
375 return `file://${path.join(path.resolve(__dirname, '../../'), SQLiteFileName)}`;
377 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
381 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
382 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
385 private static isUndefined(obj
: unknown
): boolean {
386 return typeof obj
=== 'undefined';
389 private static handleFileException(
393 error
: NodeJS
.ErrnoException
,
394 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
396 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
397 if (error
.code
=== 'ENOENT') {
399 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
402 } else if (error
.code
=== 'EEXIST') {
404 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
407 } else if (error
.code
=== 'EACCES') {
409 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
414 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
418 if (params
?.throwError
) {