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 { WorkerProcessType
} from
'../types/Worker';
15 import chalk from
'chalk';
17 import path from
'path';
19 export default class Configuration
{
20 private static configurationFilePath
= path
.join(
21 path
.resolve(__dirname
, '../'),
26 private static configurationFileWatcher
: fs
.FSWatcher
;
27 private static configuration
: ConfigurationData
| null = null;
28 private static configurationChangeCallback
: () => Promise
<void>;
30 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
31 Configuration
.configurationChangeCallback
= cb
;
34 static getLogStatisticsInterval(): number {
35 Configuration
.warnDeprecatedConfigurationKey(
36 'statisticsDisplayInterval',
38 "Use 'logStatisticsInterval' instead"
41 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
42 ? Configuration
.getConfig().logStatisticsInterval
46 static getUIWebSocketServer(): UIWebSocketServerConfiguration
{
47 let options
: ServerOptions
= {
48 host
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_HOST
,
49 port
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_PORT
,
51 let uiWebSocketServerConfiguration
: UIWebSocketServerConfiguration
= {
55 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
57 Configuration
.objectHasOwnProperty(Configuration
.getConfig().uiWebSocketServer
, 'options')
61 ...(Configuration
.objectHasOwnProperty(
62 Configuration
.getConfig().uiWebSocketServer
.options
,
64 ) && { host
: Configuration
.getConfig().uiWebSocketServer
.options
.host
}),
65 ...(Configuration
.objectHasOwnProperty(
66 Configuration
.getConfig().uiWebSocketServer
.options
,
68 ) && { port
: Configuration
.getConfig().uiWebSocketServer
.options
.port
}),
71 uiWebSocketServerConfiguration
= {
72 ...uiWebSocketServerConfiguration
,
73 ...(Configuration
.objectHasOwnProperty(
74 Configuration
.getConfig().uiWebSocketServer
,
76 ) && { enabled
: Configuration
.getConfig().uiWebSocketServer
.enabled
}),
80 return uiWebSocketServerConfiguration
;
83 static getPerformanceStorage(): StorageConfiguration
{
84 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
85 let storageConfiguration
: StorageConfiguration
= {
87 type: StorageType
.JSON_FILE
,
88 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
90 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
91 storageConfiguration
= {
92 ...storageConfiguration
,
93 ...(Configuration
.objectHasOwnProperty(
94 Configuration
.getConfig().performanceStorage
,
96 ) && { enabled
: Configuration
.getConfig().performanceStorage
.enabled
}),
97 ...(Configuration
.objectHasOwnProperty(
98 Configuration
.getConfig().performanceStorage
,
100 ) && { type: Configuration
.getConfig().performanceStorage
.type }),
101 ...(Configuration
.objectHasOwnProperty(
102 Configuration
.getConfig().performanceStorage
,
105 uri
: this.getDefaultPerformanceStorageUri(
106 Configuration
.getConfig()?.performanceStorage
?.type ?? StorageType
.JSON_FILE
111 return storageConfiguration
;
114 static getAutoReconnectMaxRetries(): number {
115 Configuration
.warnDeprecatedConfigurationKey(
116 'autoReconnectTimeout',
118 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
120 Configuration
.warnDeprecatedConfigurationKey(
123 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
125 Configuration
.warnDeprecatedConfigurationKey(
126 'autoReconnectMaxRetries',
128 'Use it in charging station template instead'
131 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
132 return Configuration
.getConfig().autoReconnectMaxRetries
;
136 static getStationTemplateUrls(): StationTemplateUrl
[] {
137 Configuration
.warnDeprecatedConfigurationKey(
138 'stationTemplateURLs',
140 "Use 'stationTemplateUrls' instead"
142 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
143 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
144 'stationTemplateURLs'
145 ] as StationTemplateUrl
[]);
146 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
147 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
149 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
151 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
156 return Configuration
.getConfig().stationTemplateUrls
;
159 static getWorkerProcess(): WorkerProcessType
{
160 Configuration
.warnDeprecatedConfigurationKey(
163 "Use 'workerProcess' to define the type of worker process to use instead"
165 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
166 ? Configuration
.getConfig().workerProcess
167 : WorkerProcessType
.WORKER_SET
;
170 static getWorkerStartDelay(): number {
171 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
172 ? Configuration
.getConfig().workerStartDelay
173 : Constants
.WORKER_START_DELAY
;
176 static getElementStartDelay(): number {
177 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'elementStartDelay')
178 ? Configuration
.getConfig().elementStartDelay
179 : Constants
.ELEMENT_START_DELAY
;
182 static getWorkerPoolMinSize(): number {
183 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMinSize')
184 ? Configuration
.getConfig().workerPoolMinSize
185 : Constants
.DEFAULT_WORKER_POOL_MIN_SIZE
;
188 static getWorkerPoolMaxSize(): number {
189 Configuration
.warnDeprecatedConfigurationKey(
192 "Use 'workerPoolMaxSize' instead"
194 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMaxSize')
195 ? Configuration
.getConfig().workerPoolMaxSize
196 : Constants
.DEFAULT_WORKER_POOL_MAX_SIZE
;
199 static getWorkerPoolStrategy(): WorkerChoiceStrategy
{
200 return Configuration
.getConfig().workerPoolStrategy
;
203 static getChargingStationsPerWorker(): number {
204 return Configuration
.objectHasOwnProperty(
205 Configuration
.getConfig(),
206 'chargingStationsPerWorker'
208 ? Configuration
.getConfig().chargingStationsPerWorker
209 : Constants
.DEFAULT_CHARGING_STATIONS_PER_WORKER
;
212 static getLogConsole(): boolean {
213 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
214 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
215 ? Configuration
.getConfig().logConsole
219 static getLogFormat(): string {
220 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
221 ? Configuration
.getConfig().logFormat
225 static getLogRotate(): boolean {
226 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
227 ? Configuration
.getConfig().logRotate
231 static getLogMaxFiles(): number {
232 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
233 ? Configuration
.getConfig().logMaxFiles
237 static getLogLevel(): string {
238 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
239 ? Configuration
.getConfig().logLevel
.toLowerCase()
243 static getLogFile(): string {
244 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
245 ? Configuration
.getConfig().logFile
249 static getLogErrorFile(): string {
250 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
251 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
252 ? Configuration
.getConfig().logErrorFile
256 static getSupervisionUrls(): string | string[] {
257 Configuration
.warnDeprecatedConfigurationKey(
260 "Use 'supervisionUrls' instead"
262 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
263 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
267 return Configuration
.getConfig().supervisionUrls
;
270 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
271 Configuration
.warnDeprecatedConfigurationKey(
272 'distributeStationToTenantEqually',
274 "Use 'supervisionUrlDistribution' instead"
276 Configuration
.warnDeprecatedConfigurationKey(
277 'distributeStationsToTenantsEqually',
279 "Use 'supervisionUrlDistribution' instead"
281 return Configuration
.objectHasOwnProperty(
282 Configuration
.getConfig(),
283 'supervisionUrlDistribution'
285 ? Configuration
.getConfig().supervisionUrlDistribution
286 : SupervisionUrlDistribution
.ROUND_ROBIN
;
289 private static logPrefix(): string {
290 return new Date().toLocaleString() + ' Simulator configuration |';
293 private static warnDeprecatedConfigurationKey(
295 sectionName
?: string,
298 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
301 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
302 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
][key
])
305 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
306 logMsgToAppend && '. ' + logMsgToAppend
309 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
311 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
312 logMsgToAppend && '. ' + logMsgToAppend
318 // Read the config file
319 private static getConfig(): ConfigurationData
{
320 if (!Configuration
.configuration
) {
322 Configuration
.configuration
= JSON
.parse(
323 fs
.readFileSync(Configuration
.configurationFilePath
, 'utf8')
324 ) as ConfigurationData
;
326 Configuration
.handleFileException(
327 Configuration
.logPrefix(),
329 Configuration
.configurationFilePath
,
333 if (!Configuration
.configurationFileWatcher
) {
334 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
337 return Configuration
.configuration
;
340 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
342 return fs
.watch(Configuration
.configurationFilePath
, (event
, filename
): void => {
343 if (filename
&& event
=== 'change') {
344 // Nullify to force configuration file reading
345 Configuration
.configuration
= null;
346 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
347 Configuration
.configurationChangeCallback().catch((error
) => {
348 throw typeof error
=== 'string' ? new Error(error
) : error
;
354 Configuration
.handleFileException(
355 Configuration
.logPrefix(),
357 Configuration
.configurationFilePath
,
363 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
364 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
365 switch (storageType
) {
366 case StorageType
.JSON_FILE
:
367 return `file://${path.join(
368 path.resolve(__dirname, '../../'),
369 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
371 case StorageType
.SQLITE
:
372 return `file://${path.join(path.resolve(__dirname, '../../'), SQLiteFileName)}`;
374 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
378 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
379 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
382 private static isUndefined(obj
: unknown
): boolean {
383 return typeof obj
=== 'undefined';
386 private static handleFileException(
390 error
: NodeJS
.ErrnoException
,
391 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
393 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
394 if (error
.code
=== 'ENOENT') {
396 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
399 } else if (error
.code
=== 'EEXIST') {
401 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
404 } else if (error
.code
=== 'EACCES') {
406 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
411 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
415 if (params
?.throwError
) {