1 import ConfigurationData
, {
4 SupervisionUrlDistribution
,
5 UIWebSocketServerConfiguration
,
6 } from
'../types/ConfigurationData';
8 import Constants from
'./Constants';
9 import { EmptyObject
} from
'../types/EmptyObject';
10 import { FileType
} from
'../types/FileType';
11 import { HandleErrorParams
} from
'../types/Error';
12 import { ServerOptions
} from
'ws';
13 import { StorageType
} from
'../types/Storage';
14 import type { WorkerChoiceStrategy
} from
'poolifier';
15 import WorkerConstants from
'../worker/WorkerConstants';
16 import { WorkerProcessType
} from
'../types/Worker';
17 import chalk from
'chalk';
19 import path from
'path';
21 export default class Configuration
{
22 private static configurationFile
= path
.join(
23 path
.resolve(__dirname
, '../'),
28 private static configurationFileWatcher
: fs
.FSWatcher
;
29 private static configuration
: ConfigurationData
| null = null;
30 private static configurationChangeCallback
: () => Promise
<void>;
32 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
33 Configuration
.configurationChangeCallback
= cb
;
36 static getLogStatisticsInterval(): number {
37 Configuration
.warnDeprecatedConfigurationKey(
38 'statisticsDisplayInterval',
40 "Use 'logStatisticsInterval' instead"
43 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
44 ? Configuration
.getConfig().logStatisticsInterval
45 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
48 static getUIWebSocketServer(): UIWebSocketServerConfiguration
{
49 let options
: ServerOptions
= {
50 host
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_HOST
,
51 port
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_PORT
,
53 let uiWebSocketServerConfiguration
: UIWebSocketServerConfiguration
= {
57 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
59 Configuration
.objectHasOwnProperty(Configuration
.getConfig().uiWebSocketServer
, 'options')
63 ...(Configuration
.objectHasOwnProperty(
64 Configuration
.getConfig().uiWebSocketServer
.options
,
66 ) && { host
: Configuration
.getConfig().uiWebSocketServer
.options
.host
}),
67 ...(Configuration
.objectHasOwnProperty(
68 Configuration
.getConfig().uiWebSocketServer
.options
,
70 ) && { port
: Configuration
.getConfig().uiWebSocketServer
.options
.port
}),
73 uiWebSocketServerConfiguration
= {
74 ...uiWebSocketServerConfiguration
,
75 ...(Configuration
.objectHasOwnProperty(
76 Configuration
.getConfig().uiWebSocketServer
,
78 ) && { enabled
: Configuration
.getConfig().uiWebSocketServer
.enabled
}),
82 return uiWebSocketServerConfiguration
;
85 static getPerformanceStorage(): StorageConfiguration
{
86 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
87 let storageConfiguration
: StorageConfiguration
= {
89 type: StorageType
.JSON_FILE
,
90 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
92 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
93 storageConfiguration
= {
94 ...storageConfiguration
,
95 ...(Configuration
.objectHasOwnProperty(
96 Configuration
.getConfig().performanceStorage
,
98 ) && { enabled
: Configuration
.getConfig().performanceStorage
.enabled
}),
99 ...(Configuration
.objectHasOwnProperty(
100 Configuration
.getConfig().performanceStorage
,
102 ) && { type: Configuration
.getConfig().performanceStorage
.type }),
103 ...(Configuration
.objectHasOwnProperty(
104 Configuration
.getConfig().performanceStorage
,
107 uri
: this.getDefaultPerformanceStorageUri(
108 Configuration
.getConfig()?.performanceStorage
?.type ?? StorageType
.JSON_FILE
113 return storageConfiguration
;
116 static getAutoReconnectMaxRetries(): number {
117 Configuration
.warnDeprecatedConfigurationKey(
118 'autoReconnectTimeout',
120 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
122 Configuration
.warnDeprecatedConfigurationKey(
125 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
127 Configuration
.warnDeprecatedConfigurationKey(
128 'autoReconnectMaxRetries',
130 'Use it in charging station template instead'
133 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
134 return Configuration
.getConfig().autoReconnectMaxRetries
;
138 static getStationTemplateUrls(): StationTemplateUrl
[] {
139 Configuration
.warnDeprecatedConfigurationKey(
140 'stationTemplateURLs',
142 "Use 'stationTemplateUrls' instead"
144 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
145 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
146 'stationTemplateURLs'
147 ] as StationTemplateUrl
[]);
148 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
149 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
151 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
153 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
158 return Configuration
.getConfig().stationTemplateUrls
;
161 static getWorkerProcess(): WorkerProcessType
{
162 Configuration
.warnDeprecatedConfigurationKey(
165 "Use 'workerProcess' to define the type of worker process model to use instead"
167 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
168 ? Configuration
.getConfig().workerProcess
169 : WorkerProcessType
.WORKER_SET
;
172 static getWorkerStartDelay(): number {
173 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
174 ? Configuration
.getConfig().workerStartDelay
175 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
;
178 static getElementStartDelay(): number {
179 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'elementStartDelay')
180 ? Configuration
.getConfig().elementStartDelay
181 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
;
184 static getWorkerPoolMinSize(): number {
185 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMinSize')
186 ? Configuration
.getConfig().workerPoolMinSize
187 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
;
190 static getWorkerPoolMaxSize(): number {
191 Configuration
.warnDeprecatedConfigurationKey(
194 "Use 'workerPoolMaxSize' instead"
196 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMaxSize')
197 ? Configuration
.getConfig().workerPoolMaxSize
198 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
;
201 static getWorkerPoolStrategy(): WorkerChoiceStrategy
{
202 return Configuration
.getConfig().workerPoolStrategy
;
205 static getChargingStationsPerWorker(): number {
206 return Configuration
.objectHasOwnProperty(
207 Configuration
.getConfig(),
208 'chargingStationsPerWorker'
210 ? Configuration
.getConfig().chargingStationsPerWorker
211 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
;
214 static getLogConsole(): boolean {
215 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
216 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
217 ? Configuration
.getConfig().logConsole
221 static getLogFormat(): string {
222 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
223 ? Configuration
.getConfig().logFormat
227 static getLogRotate(): boolean {
228 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
229 ? Configuration
.getConfig().logRotate
233 static getLogMaxFiles(): number {
234 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
235 ? Configuration
.getConfig().logMaxFiles
239 static getLogLevel(): string {
240 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
241 ? Configuration
.getConfig().logLevel
.toLowerCase()
245 static getLogFile(): string {
246 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
247 ? Configuration
.getConfig().logFile
251 static getLogErrorFile(): string {
252 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
253 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
254 ? Configuration
.getConfig().logErrorFile
258 static getSupervisionUrls(): string | string[] {
259 Configuration
.warnDeprecatedConfigurationKey(
262 "Use 'supervisionUrls' instead"
264 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
265 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
269 return Configuration
.getConfig().supervisionUrls
;
272 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
273 Configuration
.warnDeprecatedConfigurationKey(
274 'distributeStationToTenantEqually',
276 "Use 'supervisionUrlDistribution' instead"
278 Configuration
.warnDeprecatedConfigurationKey(
279 'distributeStationsToTenantsEqually',
281 "Use 'supervisionUrlDistribution' instead"
283 return Configuration
.objectHasOwnProperty(
284 Configuration
.getConfig(),
285 'supervisionUrlDistribution'
287 ? Configuration
.getConfig().supervisionUrlDistribution
288 : SupervisionUrlDistribution
.ROUND_ROBIN
;
291 private static logPrefix(): string {
292 return new Date().toLocaleString() + ' Simulator configuration |';
295 private static warnDeprecatedConfigurationKey(
297 sectionName
?: string,
302 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
303 !Configuration
.isUndefined(
304 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
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
.configurationFile
, 'utf8')
327 ) as ConfigurationData
;
329 Configuration
.handleFileException(
330 Configuration
.logPrefix(),
331 FileType
.Configuration
,
332 Configuration
.configurationFile
,
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
.configurationFile
, (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(),
359 FileType
.Configuration
,
360 Configuration
.configurationFile
,
361 error
as NodeJS
.ErrnoException
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
) {