1 import ConfigurationData
, {
4 SupervisionUrlDistribution
,
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 getUIServer(): UIServerConfiguration
{
49 let options
: ServerOptions
= {
50 host
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_HOST
,
51 port
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_PORT
,
53 let uiServerConfiguration
: UIServerConfiguration
= {
57 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiServer')) {
58 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig().uiServer
, 'options')) {
61 ...(Configuration
.objectHasOwnProperty(
62 Configuration
.getConfig().uiServer
.options
,
64 ) && { host
: Configuration
.getConfig().uiServer
.options
.host
}),
65 ...(Configuration
.objectHasOwnProperty(
66 Configuration
.getConfig().uiServer
.options
,
68 ) && { port
: Configuration
.getConfig().uiServer
.options
.port
}),
71 uiServerConfiguration
= {
72 ...uiServerConfiguration
,
73 ...(Configuration
.objectHasOwnProperty(Configuration
.getConfig().uiServer
, 'enabled') && {
74 enabled
: Configuration
.getConfig().uiServer
.enabled
,
79 return uiServerConfiguration
;
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 model 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 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
;
175 static getElementStartDelay(): number {
176 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'elementStartDelay')
177 ? Configuration
.getConfig().elementStartDelay
178 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
;
181 static getWorkerPoolMinSize(): number {
182 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerPoolMinSize')
183 ? Configuration
.getConfig().workerPoolMinSize
184 : WorkerConstants
.DEFAULT_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 : WorkerConstants
.DEFAULT_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 : WorkerConstants
.DEFAULT_ELEMENTS_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,
299 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
300 !Configuration
.isUndefined(
301 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[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
.configurationFile
, 'utf8')
324 ) as ConfigurationData
;
326 Configuration
.handleFileException(
327 Configuration
.logPrefix(),
328 FileType
.Configuration
,
329 Configuration
.configurationFile
,
330 error
as NodeJS
.ErrnoException
333 if (!Configuration
.configurationFileWatcher
) {
334 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
337 return Configuration
.configuration
;
340 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
342 return fs
.watch(Configuration
.configurationFile
, (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(),
356 FileType
.Configuration
,
357 Configuration
.configurationFile
,
358 error
as NodeJS
.ErrnoException
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
) {