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
, '../'),
25 private static configurationFileWatcher
: fs
.FSWatcher
;
26 private static configuration
: ConfigurationData
| null = null;
27 private static configurationChangeCallback
: () => Promise
<void>;
29 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
30 Configuration
.configurationChangeCallback
= cb
;
33 static getLogStatisticsInterval(): number {
34 Configuration
.warnDeprecatedConfigurationKey(
35 'statisticsDisplayInterval',
37 "Use 'logStatisticsInterval' instead"
40 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
41 ? Configuration
.getConfig().logStatisticsInterval
45 static getUIWebSocketServer(): UIWebSocketServerConfiguration
{
46 let options
: ServerOptions
= {
47 host
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_HOST
,
48 port
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_PORT
,
50 let uiWebSocketServerConfiguration
: UIWebSocketServerConfiguration
= {
54 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
56 Configuration
.objectHasOwnProperty(Configuration
.getConfig().uiWebSocketServer
, 'options')
60 ...(Configuration
.objectHasOwnProperty(
61 Configuration
.getConfig().uiWebSocketServer
.options
,
63 ) && { host
: Configuration
.getConfig().uiWebSocketServer
.options
.host
}),
64 ...(Configuration
.objectHasOwnProperty(
65 Configuration
.getConfig().uiWebSocketServer
.options
,
67 ) && { port
: Configuration
.getConfig().uiWebSocketServer
.options
.port
}),
70 uiWebSocketServerConfiguration
= {
71 ...uiWebSocketServerConfiguration
,
72 ...(Configuration
.objectHasOwnProperty(
73 Configuration
.getConfig().uiWebSocketServer
,
75 ) && { enabled
: Configuration
.getConfig().uiWebSocketServer
.enabled
}),
79 return uiWebSocketServerConfiguration
;
82 static getPerformanceStorage(): StorageConfiguration
{
83 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
84 let storageConfiguration
: StorageConfiguration
= {
86 type: StorageType
.JSON_FILE
,
87 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
89 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
90 storageConfiguration
= {
91 ...storageConfiguration
,
92 ...(Configuration
.objectHasOwnProperty(
93 Configuration
.getConfig().performanceStorage
,
95 ) && { enabled
: Configuration
.getConfig().performanceStorage
.enabled
}),
96 ...(Configuration
.objectHasOwnProperty(
97 Configuration
.getConfig().performanceStorage
,
99 ) && { type: Configuration
.getConfig().performanceStorage
.type }),
100 ...(Configuration
.objectHasOwnProperty(
101 Configuration
.getConfig().performanceStorage
,
104 uri
: this.getDefaultPerformanceStorageUri(
105 Configuration
.getConfig()?.performanceStorage
?.type ?? StorageType
.JSON_FILE
110 return storageConfiguration
;
113 static getAutoReconnectMaxRetries(): number {
114 Configuration
.warnDeprecatedConfigurationKey(
115 'autoReconnectTimeout',
117 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
119 Configuration
.warnDeprecatedConfigurationKey(
122 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
124 Configuration
.warnDeprecatedConfigurationKey(
125 'autoReconnectMaxRetries',
127 'Use it in charging station template instead'
130 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
131 return Configuration
.getConfig().autoReconnectMaxRetries
;
135 static getStationTemplateUrls(): StationTemplateUrl
[] {
136 Configuration
.warnDeprecatedConfigurationKey(
137 'stationTemplateURLs',
139 "Use 'stationTemplateUrls' instead"
141 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
142 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
143 'stationTemplateURLs'
144 ] as StationTemplateUrl
[]);
145 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
146 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
148 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
150 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
155 return Configuration
.getConfig().stationTemplateUrls
;
158 static getWorkerProcess(): WorkerProcessType
{
159 Configuration
.warnDeprecatedConfigurationKey(
162 "Use 'workerProcess' to define the type of worker process to use instead"
164 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
165 ? Configuration
.getConfig().workerProcess
166 : WorkerProcessType
.WORKER_SET
;
169 static getWorkerStartDelay(): number {
170 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
171 ? Configuration
.getConfig().workerStartDelay
172 : Constants
.WORKER_START_DELAY
;
175 static getElementStartDelay(): number {
176 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'elementStartDelay')
177 ? Configuration
.getConfig().elementStartDelay
178 : Constants
.ELEMENT_START_DELAY
;
181 static getWorkerPoolMinSize(): number {
182 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMinSize')
183 ? Configuration
.getConfig().workerPoolMinSize
184 : Constants
.DEFAULT_WORKER_POOL_MIN_SIZE
;
187 static getWorkerPoolMaxSize(): number {
188 Configuration
.warnDeprecatedConfigurationKey(
191 "Use 'workerPoolMaxSize' instead"
193 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMaxSize')
194 ? Configuration
.getConfig().workerPoolMaxSize
195 : Constants
.DEFAULT_WORKER_POOL_MAX_SIZE
;
198 static getWorkerPoolStrategy(): WorkerChoiceStrategy
{
199 return Configuration
.getConfig().workerPoolStrategy
;
202 static getChargingStationsPerWorker(): number {
203 return Configuration
.objectHasOwnProperty(
204 Configuration
.getConfig(),
205 'chargingStationsPerWorker'
207 ? Configuration
.getConfig().chargingStationsPerWorker
208 : Constants
.DEFAULT_CHARGING_STATIONS_PER_WORKER
;
211 static getLogConsole(): boolean {
212 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
213 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
214 ? Configuration
.getConfig().logConsole
218 static getLogFormat(): string {
219 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
220 ? Configuration
.getConfig().logFormat
224 static getLogRotate(): boolean {
225 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
226 ? Configuration
.getConfig().logRotate
230 static getLogMaxFiles(): number {
231 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
232 ? Configuration
.getConfig().logMaxFiles
236 static getLogLevel(): string {
237 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
238 ? Configuration
.getConfig().logLevel
.toLowerCase()
242 static getLogFile(): string {
243 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
244 ? Configuration
.getConfig().logFile
248 static getLogErrorFile(): string {
249 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
250 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
251 ? Configuration
.getConfig().logErrorFile
255 static getSupervisionUrls(): string | string[] {
256 Configuration
.warnDeprecatedConfigurationKey(
259 "Use 'supervisionUrls' instead"
261 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
262 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
266 return Configuration
.getConfig().supervisionUrls
;
269 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
270 Configuration
.warnDeprecatedConfigurationKey(
271 'distributeStationToTenantEqually',
273 "Use 'supervisionUrlDistribution' instead"
275 Configuration
.warnDeprecatedConfigurationKey(
276 'distributeStationsToTenantsEqually',
278 "Use 'supervisionUrlDistribution' instead"
280 return Configuration
.objectHasOwnProperty(
281 Configuration
.getConfig(),
282 'supervisionUrlDistribution'
284 ? Configuration
.getConfig().supervisionUrlDistribution
285 : SupervisionUrlDistribution
.ROUND_ROBIN
;
288 private static logPrefix(): string {
289 return new Date().toLocaleString() + ' Simulator configuration |';
292 private static warnDeprecatedConfigurationKey(
294 sectionName
?: string,
297 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
300 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
301 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
][key
])
304 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
305 logMsgToAppend && '. ' + logMsgToAppend
308 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
310 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
311 logMsgToAppend && '. ' + logMsgToAppend
317 // Read the config file
318 private static getConfig(): ConfigurationData
{
319 if (!Configuration
.configuration
) {
321 Configuration
.configuration
= JSON
.parse(
322 fs
.readFileSync(Configuration
.configurationFilePath
, 'utf8')
323 ) as ConfigurationData
;
325 Configuration
.handleFileException(
326 Configuration
.logPrefix(),
328 Configuration
.configurationFilePath
,
332 if (!Configuration
.configurationFileWatcher
) {
333 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
336 return Configuration
.configuration
;
339 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
341 return fs
.watch(Configuration
.configurationFilePath
, (event
, filename
): void => {
342 if (filename
&& event
=== 'change') {
343 // Nullify to force configuration file reading
344 Configuration
.configuration
= null;
345 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
346 Configuration
.configurationChangeCallback().catch((error
) => {
347 throw typeof error
=== 'string' ? new Error(error
) : error
;
353 Configuration
.handleFileException(
354 Configuration
.logPrefix(),
356 Configuration
.configurationFilePath
,
362 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
363 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
364 switch (storageType
) {
365 case StorageType
.JSON_FILE
:
366 return `file://${path.join(
367 path.resolve(__dirname, '../../'),
368 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
370 case StorageType
.SQLITE
:
371 return `file://${path.join(path.resolve(__dirname, '../../'), SQLiteFileName)}`;
373 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
377 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
378 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
381 private static isUndefined(obj
: unknown
): boolean {
382 return typeof obj
=== 'undefined';
385 private static handleFileException(
389 error
: NodeJS
.ErrnoException
,
390 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
392 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
393 if (error
.code
=== 'ENOENT') {
395 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
398 } else if (error
.code
=== 'EEXIST') {
400 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
403 } else if (error
.code
=== 'EACCES') {
405 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
410 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
414 if (params
?.throwError
) {