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,
301 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
302 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[
307 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
308 logMsgToAppend && '. ' + logMsgToAppend
311 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
313 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
314 logMsgToAppend && '. ' + logMsgToAppend
320 // Read the config file
321 private static getConfig(): ConfigurationData
{
322 if (!Configuration
.configuration
) {
324 Configuration
.configuration
= JSON
.parse(
325 fs
.readFileSync(Configuration
.configurationFilePath
, 'utf8')
326 ) as ConfigurationData
;
328 Configuration
.handleFileException(
329 Configuration
.logPrefix(),
331 Configuration
.configurationFilePath
,
332 error
as NodeJS
.ErrnoException
335 if (!Configuration
.configurationFileWatcher
) {
336 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
339 return Configuration
.configuration
;
342 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
344 return fs
.watch(Configuration
.configurationFilePath
, (event
, filename
): void => {
345 if (filename
&& event
=== 'change') {
346 // Nullify to force configuration file reading
347 Configuration
.configuration
= null;
348 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
349 Configuration
.configurationChangeCallback().catch((error
) => {
350 throw typeof error
=== 'string' ? new Error(error
) : error
;
356 Configuration
.handleFileException(
357 Configuration
.logPrefix(),
359 Configuration
.configurationFilePath
,
365 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
366 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
367 switch (storageType
) {
368 case StorageType
.JSON_FILE
:
369 return `file://${path.join(
370 path.resolve(__dirname, '../../'),
371 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
373 case StorageType
.SQLITE
:
374 return `file://${path.join(path.resolve(__dirname, '../../'), SQLiteFileName)}`;
376 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
380 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
381 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
384 private static isUndefined(obj
: unknown
): boolean {
385 return typeof obj
=== 'undefined';
388 private static handleFileException(
392 error
: NodeJS
.ErrnoException
,
393 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
395 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
396 if (error
.code
=== 'ENOENT') {
398 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
401 } else if (error
.code
=== 'EEXIST') {
403 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
406 } else if (error
.code
=== 'EACCES') {
408 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
413 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
417 if (params
?.throwError
) {