Fix WS reconnect timeout calculation
[e-mobility-charging-stations-simulator.git] / src / utils / Configuration.ts
CommitLineData
e7aeea18 1import ConfigurationData, {
66271092 2 ServerOptions,
e7aeea18
JB
3 StationTemplateUrl,
4 StorageConfiguration,
5 SupervisionUrlDistribution,
675fa8e3 6 UIServerConfiguration,
e7aeea18 7} from '../types/ConfigurationData';
e118beaa 8
322c9192 9import Constants from './Constants';
717c1e56 10import { EmptyObject } from '../types/EmptyObject';
a95873d8 11import { FileType } from '../types/FileType';
e0a50bcd 12import { HandleErrorParams } from '../types/Error';
72f041bd 13import { StorageType } from '../types/Storage';
9efbac5b 14import type { WorkerChoiceStrategy } from 'poolifier';
3fa0f0ed 15import WorkerConstants from '../worker/WorkerConstants';
a4624c96 16import { WorkerProcessType } from '../types/Worker';
8eac9a09 17import chalk from 'chalk';
3f40bc9c 18import fs from 'fs';
bf1866b2 19import path from 'path';
7dde0b73 20
3f40bc9c 21export default class Configuration {
a95873d8 22 private static configurationFile = path.join(
e7aeea18
JB
23 path.resolve(__dirname, '../'),
24 'assets',
25 'config.json'
26 );
10068088 27
ded13d97 28 private static configurationFileWatcher: fs.FSWatcher;
6e0964c8 29 private static configuration: ConfigurationData | null = null;
e57acf6a
JB
30 private static configurationChangeCallback: () => Promise<void>;
31
32 static setConfigurationChangeCallback(cb: () => Promise<void>): void {
33 Configuration.configurationChangeCallback = cb;
34 }
7dde0b73 35
72f041bd 36 static getLogStatisticsInterval(): number {
e7aeea18
JB
37 Configuration.warnDeprecatedConfigurationKey(
38 'statisticsDisplayInterval',
39 null,
40 "Use 'logStatisticsInterval' instead"
41 );
7dde0b73 42 // Read conf
e7aeea18
JB
43 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logStatisticsInterval')
44 ? Configuration.getConfig().logStatisticsInterval
25f5a959 45 : Constants.DEFAULT_LOG_STATISTICS_INTERVAL;
72f041bd
JB
46 }
47
675fa8e3 48 static getUIServer(): UIServerConfiguration {
66271092
JB
49 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'uiWebSocketServer')) {
50 console.error(
51 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
52 );
53 }
6a49ad23
JB
54 let options: ServerOptions = {
55 host: Constants.DEFAULT_UI_WEBSOCKET_SERVER_HOST,
e7aeea18 56 port: Constants.DEFAULT_UI_WEBSOCKET_SERVER_PORT,
6a49ad23 57 };
675fa8e3 58 let uiServerConfiguration: UIServerConfiguration = {
6a49ad23 59 enabled: true,
e7aeea18 60 options,
6a49ad23 61 };
675fa8e3
JB
62 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'uiServer')) {
63 if (Configuration.objectHasOwnProperty(Configuration.getConfig().uiServer, 'options')) {
6a49ad23 64 options = {
1ba1e8fb 65 ...options,
e7aeea18 66 ...(Configuration.objectHasOwnProperty(
675fa8e3 67 Configuration.getConfig().uiServer.options,
e7aeea18 68 'host'
675fa8e3 69 ) && { host: Configuration.getConfig().uiServer.options.host }),
e7aeea18 70 ...(Configuration.objectHasOwnProperty(
675fa8e3 71 Configuration.getConfig().uiServer.options,
e7aeea18 72 'port'
675fa8e3 73 ) && { port: Configuration.getConfig().uiServer.options.port }),
6a49ad23
JB
74 };
75 }
675fa8e3
JB
76 uiServerConfiguration = {
77 ...uiServerConfiguration,
78 ...(Configuration.objectHasOwnProperty(Configuration.getConfig().uiServer, 'enabled') && {
79 enabled: Configuration.getConfig().uiServer.enabled,
80 }),
e7aeea18 81 options,
6a49ad23
JB
82 };
83 }
675fa8e3 84 return uiServerConfiguration;
6a49ad23
JB
85 }
86
72f041bd 87 static getPerformanceStorage(): StorageConfiguration {
e7aeea18 88 Configuration.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
6a49ad23
JB
89 let storageConfiguration: StorageConfiguration = {
90 enabled: false,
91 type: StorageType.JSON_FILE,
e7aeea18 92 uri: this.getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
6a49ad23 93 };
72f041bd 94 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'performanceStorage')) {
e7aeea18 95 storageConfiguration = {
1ba1e8fb 96 ...storageConfiguration,
e7aeea18
JB
97 ...(Configuration.objectHasOwnProperty(
98 Configuration.getConfig().performanceStorage,
99 'enabled'
100 ) && { enabled: Configuration.getConfig().performanceStorage.enabled }),
101 ...(Configuration.objectHasOwnProperty(
102 Configuration.getConfig().performanceStorage,
103 'type'
104 ) && { type: Configuration.getConfig().performanceStorage.type }),
105 ...(Configuration.objectHasOwnProperty(
106 Configuration.getConfig().performanceStorage,
107 'uri'
108 ) && {
109 uri: this.getDefaultPerformanceStorageUri(
110 Configuration.getConfig()?.performanceStorage?.type ?? StorageType.JSON_FILE
111 ),
112 }),
72f041bd 113 };
72f041bd
JB
114 }
115 return storageConfiguration;
7dde0b73
JB
116 }
117
9ccca265 118 static getAutoReconnectMaxRetries(): number {
e7aeea18
JB
119 Configuration.warnDeprecatedConfigurationKey(
120 'autoReconnectTimeout',
121 null,
122 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
123 );
124 Configuration.warnDeprecatedConfigurationKey(
125 'connectionTimeout',
126 null,
127 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
128 );
129 Configuration.warnDeprecatedConfigurationKey(
130 'autoReconnectMaxRetries',
131 null,
132 'Use it in charging station template instead'
133 );
7dde0b73 134 // Read conf
963ee397 135 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'autoReconnectMaxRetries')) {
3574dfd3
JB
136 return Configuration.getConfig().autoReconnectMaxRetries;
137 }
7dde0b73
JB
138 }
139
1f5df42a 140 static getStationTemplateUrls(): StationTemplateUrl[] {
e7aeea18
JB
141 Configuration.warnDeprecatedConfigurationKey(
142 'stationTemplateURLs',
143 null,
144 "Use 'stationTemplateUrls' instead"
145 );
146 !Configuration.isUndefined(Configuration.getConfig()['stationTemplateURLs']) &&
147 (Configuration.getConfig().stationTemplateUrls = Configuration.getConfig()[
148 'stationTemplateURLs'
149 ] as StationTemplateUrl[]);
1f5df42a
JB
150 Configuration.getConfig().stationTemplateUrls.forEach((stationUrl: StationTemplateUrl) => {
151 if (!Configuration.isUndefined(stationUrl['numberOfStation'])) {
e7aeea18
JB
152 console.error(
153 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
154 stationUrl.file
155 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
156 );
eb3937cb
JB
157 }
158 });
7dde0b73 159 // Read conf
1f5df42a 160 return Configuration.getConfig().stationTemplateUrls;
7dde0b73
JB
161 }
162
a4624c96 163 static getWorkerProcess(): WorkerProcessType {
e7aeea18 164 Configuration.warnDeprecatedConfigurationKey(
e80bc579 165 'useWorkerPool',
e7aeea18 166 null,
2484ac1e 167 "Use 'workerProcess' to define the type of worker process model to use instead"
e7aeea18
JB
168 );
169 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerProcess')
170 ? Configuration.getConfig().workerProcess
171 : WorkerProcessType.WORKER_SET;
a4624c96
JB
172 }
173
322c9192 174 static getWorkerStartDelay(): number {
e7aeea18
JB
175 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerStartDelay')
176 ? Configuration.getConfig().workerStartDelay
3fa0f0ed 177 : WorkerConstants.DEFAULT_WORKER_START_DELAY;
322c9192
JB
178 }
179
4bfd80fa 180 static getElementStartDelay(): number {
e7aeea18
JB
181 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'elementStartDelay')
182 ? Configuration.getConfig().elementStartDelay
3fa0f0ed 183 : WorkerConstants.DEFAULT_ELEMENT_START_DELAY;
4bfd80fa
JB
184 }
185
a4624c96 186 static getWorkerPoolMinSize(): number {
e7aeea18
JB
187 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerPoolMinSize')
188 ? Configuration.getConfig().workerPoolMinSize
3fa0f0ed 189 : WorkerConstants.DEFAULT_POOL_MIN_SIZE;
7dde0b73
JB
190 }
191
4fa59b8a 192 static getWorkerPoolMaxSize(): number {
e7aeea18
JB
193 Configuration.warnDeprecatedConfigurationKey(
194 'workerPoolSize;',
195 null,
196 "Use 'workerPoolMaxSize' instead"
197 );
198 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerPoolMaxSize')
199 ? Configuration.getConfig().workerPoolMaxSize
3fa0f0ed 200 : WorkerConstants.DEFAULT_POOL_MAX_SIZE;
7dde0b73
JB
201 }
202
9efbac5b
JB
203 static getWorkerPoolStrategy(): WorkerChoiceStrategy {
204 return Configuration.getConfig().workerPoolStrategy;
205 }
206
3d2ff9e4 207 static getChargingStationsPerWorker(): number {
e7aeea18
JB
208 return Configuration.objectHasOwnProperty(
209 Configuration.getConfig(),
210 'chargingStationsPerWorker'
211 )
212 ? Configuration.getConfig().chargingStationsPerWorker
3fa0f0ed 213 : WorkerConstants.DEFAULT_ELEMENTS_PER_WORKER;
3d2ff9e4
J
214 }
215
7ec46a9a 216 static getLogConsole(): boolean {
e7aeea18
JB
217 Configuration.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
218 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logConsole')
219 ? Configuration.getConfig().logConsole
220 : false;
7dde0b73
JB
221 }
222
a4a21709 223 static getLogFormat(): string {
e7aeea18
JB
224 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFormat')
225 ? Configuration.getConfig().logFormat
226 : 'simple';
027b409a
JB
227 }
228
6bf6769e 229 static getLogRotate(): boolean {
e7aeea18
JB
230 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logRotate')
231 ? Configuration.getConfig().logRotate
232 : true;
6bf6769e
JB
233 }
234
235 static getLogMaxFiles(): number {
e7aeea18
JB
236 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logMaxFiles')
237 ? Configuration.getConfig().logMaxFiles
238 : 7;
6bf6769e
JB
239 }
240
324fd4ee 241 static getLogLevel(): string {
e7aeea18
JB
242 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logLevel')
243 ? Configuration.getConfig().logLevel.toLowerCase()
244 : 'info';
2e6f5966
JB
245 }
246
a4a21709 247 static getLogFile(): string {
e7aeea18
JB
248 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFile')
249 ? Configuration.getConfig().logFile
250 : 'combined.log';
7dde0b73
JB
251 }
252
7ec46a9a 253 static getLogErrorFile(): string {
e7aeea18
JB
254 Configuration.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
255 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logErrorFile')
256 ? Configuration.getConfig().logErrorFile
257 : 'error.log';
7dde0b73
JB
258 }
259
2dcfe98e 260 static getSupervisionUrls(): string | string[] {
e7aeea18
JB
261 Configuration.warnDeprecatedConfigurationKey(
262 'supervisionURLs',
263 null,
264 "Use 'supervisionUrls' instead"
265 );
266 !Configuration.isUndefined(Configuration.getConfig()['supervisionURLs']) &&
267 (Configuration.getConfig().supervisionUrls = Configuration.getConfig()[
268 'supervisionURLs'
269 ] as string[]);
7dde0b73 270 // Read conf
1f5df42a 271 return Configuration.getConfig().supervisionUrls;
7dde0b73
JB
272 }
273
2dcfe98e 274 static getSupervisionUrlDistribution(): SupervisionUrlDistribution {
e7aeea18
JB
275 Configuration.warnDeprecatedConfigurationKey(
276 'distributeStationToTenantEqually',
277 null,
278 "Use 'supervisionUrlDistribution' instead"
279 );
280 Configuration.warnDeprecatedConfigurationKey(
281 'distributeStationsToTenantsEqually',
282 null,
283 "Use 'supervisionUrlDistribution' instead"
284 );
285 return Configuration.objectHasOwnProperty(
286 Configuration.getConfig(),
287 'supervisionUrlDistribution'
288 )
289 ? Configuration.getConfig().supervisionUrlDistribution
290 : SupervisionUrlDistribution.ROUND_ROBIN;
7dde0b73 291 }
eb3937cb 292
23132a44
JB
293 private static logPrefix(): string {
294 return new Date().toLocaleString() + ' Simulator configuration |';
295 }
296
e7aeea18
JB
297 private static warnDeprecatedConfigurationKey(
298 key: string,
299 sectionName?: string,
300 logMsgToAppend = ''
301 ) {
e7aeea18
JB
302 if (
303 sectionName &&
304 !Configuration.isUndefined(Configuration.getConfig()[sectionName]) &&
455ee9cf
JB
305 !Configuration.isUndefined(
306 (Configuration.getConfig()[sectionName] as Record<string, unknown>)[key]
307 )
e7aeea18
JB
308 ) {
309 console.error(
310 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
311 logMsgToAppend && '. ' + logMsgToAppend
312 }}`
313 );
912136b1 314 } else if (!Configuration.isUndefined(Configuration.getConfig()[key])) {
e7aeea18
JB
315 console.error(
316 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
317 logMsgToAppend && '. ' + logMsgToAppend
318 }}`
319 );
eb3937cb
JB
320 }
321 }
322
323 // Read the config file
324 private static getConfig(): ConfigurationData {
325 if (!Configuration.configuration) {
23132a44 326 try {
e7aeea18 327 Configuration.configuration = JSON.parse(
a95873d8 328 fs.readFileSync(Configuration.configurationFile, 'utf8')
e7aeea18 329 ) as ConfigurationData;
23132a44 330 } catch (error) {
e7aeea18
JB
331 Configuration.handleFileException(
332 Configuration.logPrefix(),
a95873d8
JB
333 FileType.Configuration,
334 Configuration.configurationFile,
3fa0f0ed 335 error as NodeJS.ErrnoException
e7aeea18 336 );
23132a44 337 }
ded13d97
JB
338 if (!Configuration.configurationFileWatcher) {
339 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
340 }
eb3937cb
JB
341 }
342 return Configuration.configuration;
343 }
963ee397 344
ded13d97 345 private static getConfigurationFileWatcher(): fs.FSWatcher {
23132a44 346 try {
a95873d8 347 return fs.watch(Configuration.configurationFile, (event, filename): void => {
3ec10737
JB
348 if (filename && event === 'change') {
349 // Nullify to force configuration file reading
350 Configuration.configuration = null;
351 if (!Configuration.isUndefined(Configuration.configurationChangeCallback)) {
dcaf96dc
JB
352 Configuration.configurationChangeCallback().catch((error) => {
353 throw typeof error === 'string' ? new Error(error) : error;
354 });
3ec10737 355 }
23132a44
JB
356 }
357 });
358 } catch (error) {
e7aeea18
JB
359 Configuration.handleFileException(
360 Configuration.logPrefix(),
a95873d8
JB
361 FileType.Configuration,
362 Configuration.configurationFile,
363 error as NodeJS.ErrnoException
e7aeea18 364 );
23132a44 365 }
ded13d97
JB
366 }
367
1f5df42a 368 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
d5603918
JB
369 const SQLiteFileName = `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
370 switch (storageType) {
371 case StorageType.JSON_FILE:
e7aeea18
JB
372 return `file://${path.join(
373 path.resolve(__dirname, '../../'),
374 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
375 )}`;
d5603918
JB
376 case StorageType.SQLITE:
377 return `file://${path.join(path.resolve(__dirname, '../../'), SQLiteFileName)}`;
378 default:
379 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
380 }
381 }
382
73d09045 383 private static objectHasOwnProperty(object: unknown, property: string): boolean {
23132a44 384 return Object.prototype.hasOwnProperty.call(object, property) as boolean;
963ee397
JB
385 }
386
73d09045 387 private static isUndefined(obj: unknown): boolean {
963ee397
JB
388 return typeof obj === 'undefined';
389 }
23132a44 390
e7aeea18
JB
391 private static handleFileException(
392 logPrefix: string,
a95873d8 393 fileType: FileType,
e7aeea18
JB
394 filePath: string,
395 error: NodeJS.ErrnoException,
396 params: HandleErrorParams<EmptyObject> = { throwError: true }
397 ): void {
23132a44
JB
398 const prefix = logPrefix.length !== 0 ? logPrefix + ' ' : '';
399 if (error.code === 'ENOENT') {
e7aeea18
JB
400 console.error(
401 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' not found: '),
402 error
403 );
72f041bd 404 } else if (error.code === 'EEXIST') {
e7aeea18
JB
405 console.error(
406 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' already exists: '),
407 error
408 );
72f041bd 409 } else if (error.code === 'EACCES') {
e7aeea18
JB
410 console.error(
411 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' access denied: '),
412 error
413 );
23132a44 414 } else {
e7aeea18
JB
415 console.error(
416 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' error: '),
417 error
418 );
23132a44 419 }
e0a50bcd
JB
420 if (params?.throwError) {
421 throw error;
422 }
23132a44 423 }
7dde0b73 424}