]>
Commit | Line | Data |
---|---|---|
a19b897d | 1 | // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. |
b4d34251 | 2 | |
1ea7f1df | 3 | import type { Worker } from 'node:worker_threads' |
0749233f JB |
4 | |
5 | import chalk from 'chalk' | |
66a7748d | 6 | import { EventEmitter } from 'node:events' |
a33026fe | 7 | import { dirname, extname, join } from 'node:path' |
66a7748d JB |
8 | import process, { exit } from 'node:process' |
9 | import { fileURLToPath } from 'node:url' | |
c5ecc04d | 10 | import { isMainThread } from 'node:worker_threads' |
4c3f6c20 | 11 | import { availableParallelism, type MessageHandler } from 'poolifier' |
0749233f JB |
12 | |
13 | import type { AbstractUIServer } from './ui-server/AbstractUIServer.js' | |
8114d10e | 14 | |
66a7748d JB |
15 | import { version } from '../../package.json' |
16 | import { BaseError } from '../exception/index.js' | |
17 | import { type Storage, StorageFactory } from '../performance/index.js' | |
e7aeea18 | 18 | import { |
bbe10d5f | 19 | type ChargingStationData, |
3b09e788 | 20 | type ChargingStationInfo, |
71ac2bd7 | 21 | type ChargingStationOptions, |
bbe10d5f JB |
22 | type ChargingStationWorkerData, |
23 | type ChargingStationWorkerMessage, | |
24 | type ChargingStationWorkerMessageData, | |
e7aeea18 | 25 | ChargingStationWorkerMessageEvents, |
5d049829 | 26 | ConfigurationSection, |
6bd808fd | 27 | ProcedureName, |
e8237645 | 28 | type SimulatorState, |
268a74bb | 29 | type Statistics, |
5d049829 | 30 | type StorageConfiguration, |
276e05ae | 31 | type TemplateStatistics, |
5d049829 | 32 | type UIServerConfiguration, |
d1f5bfd8 | 33 | type WorkerConfiguration, |
66a7748d | 34 | } from '../types/index.js' |
fa5995d6 JB |
35 | import { |
36 | Configuration, | |
37 | Constants, | |
9bf0ef23 JB |
38 | formatDurationMilliSeconds, |
39 | generateUUID, | |
fa5995d6 JB |
40 | handleUncaughtException, |
41 | handleUnhandledRejection, | |
be0a4d4d | 42 | isAsyncFunction, |
9bf0ef23 | 43 | isNotEmptyArray, |
4c3f6c20 | 44 | logger, |
d1f5bfd8 | 45 | logPrefix, |
66a7748d | 46 | } from '../utils/index.js' |
65d22502 | 47 | import { DEFAULT_ELEMENTS_PER_WORKER, type WorkerAbstract, WorkerFactory } from '../worker/index.js' |
4c3f6c20 | 48 | import { buildTemplateName, waitChargingStationEvents } from './Helpers.js' |
4c3f6c20 | 49 | import { UIServerFactory } from './ui-server/UIServerFactory.js' |
ded13d97 | 50 | |
66a7748d | 51 | const moduleName = 'Bootstrap' |
32de5a57 | 52 | |
c4a89082 | 53 | /* eslint-disable perfectionist/sort-enums */ |
a307349b | 54 | enum exitCodes { |
42f33184 | 55 | succeeded = 0, |
0749233f | 56 | missingChargingStationsConfiguration = 1, |
42f33184 | 57 | duplicateChargingStationTemplateUrls = 2, |
2f989136 | 58 | noChargingStationTemplates = 3, |
271426fb | 59 | gracefulShutdownError = 4, |
a307349b | 60 | } |
c4a89082 | 61 | /* eslint-enable perfectionist/sort-enums */ |
e4cb2c14 | 62 | |
f130b8e6 | 63 | export class Bootstrap extends EventEmitter { |
66a7748d | 64 | private static instance: Bootstrap | null = null |
c4a89082 JB |
65 | public get numberOfChargingStationTemplates (): number { |
66 | return this.templateStatistics.size | |
67 | } | |
68 | ||
69 | public get numberOfConfiguredChargingStations (): number { | |
70 | return [...this.templateStatistics.values()].reduce( | |
71 | (accumulator, value) => accumulator + value.configured, | |
72 | 0 | |
73 | ) | |
74 | } | |
75 | ||
76 | public get numberOfProvisionedChargingStations (): number { | |
77 | return [...this.templateStatistics.values()].reduce( | |
78 | (accumulator, value) => accumulator + value.provisioned, | |
79 | 0 | |
80 | ) | |
0749233f JB |
81 | } |
82 | ||
66a7748d JB |
83 | private started: boolean |
84 | private starting: boolean | |
85 | private stopping: boolean | |
0749233f JB |
86 | private storage?: Storage |
87 | private readonly templateStatistics: Map<string, TemplateStatistics> | |
88 | private readonly uiServer: AbstractUIServer | |
a1cfaa16 | 89 | private uiServerStarted: boolean |
0749233f | 90 | private readonly version: string = version |
c4a89082 | 91 | private workerImplementation?: WorkerAbstract<ChargingStationWorkerData, ChargingStationInfo> |
579caf16 | 92 | |
c4a89082 JB |
93 | private get numberOfAddedChargingStations (): number { |
94 | return [...this.templateStatistics.values()].reduce( | |
95 | (accumulator, value) => accumulator + value.added, | |
96 | 0 | |
0749233f JB |
97 | ) |
98 | } | |
99 | ||
c4a89082 JB |
100 | private get numberOfStartedChargingStations (): number { |
101 | return [...this.templateStatistics.values()].reduce( | |
102 | (accumulator, value) => accumulator + value.started, | |
103 | 0 | |
0749233f JB |
104 | ) |
105 | } | |
106 | ||
66a7748d JB |
107 | private constructor () { |
108 | super() | |
6bd808fd | 109 | for (const signal of ['SIGINT', 'SIGQUIT', 'SIGTERM']) { |
66a7748d | 110 | process.on(signal, this.gracefulShutdown.bind(this)) |
6bd808fd | 111 | } |
4724a293 | 112 | // Enable unconditionally for now |
66a7748d JB |
113 | handleUnhandledRejection() |
114 | handleUncaughtException() | |
115 | this.started = false | |
116 | this.starting = false | |
117 | this.stopping = false | |
a1cfaa16 | 118 | this.uiServerStarted = false |
24dc52e9 | 119 | this.templateStatistics = new Map<string, TemplateStatistics>() |
36adaf06 | 120 | this.uiServer = UIServerFactory.getUIServerImplementation( |
66a7748d JB |
121 | Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer) |
122 | ) | |
42e341c4 | 123 | this.initializeCounters() |
2bb3c92f JB |
124 | this.initializeWorkerImplementation( |
125 | Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker) | |
126 | ) | |
66a7748d | 127 | Configuration.configurationChangeCallback = async () => { |
c5ecc04d JB |
128 | if (isMainThread) { |
129 | await Bootstrap.getInstance().restart() | |
130 | } | |
66a7748d | 131 | } |
ded13d97 JB |
132 | } |
133 | ||
66a7748d | 134 | public static getInstance (): Bootstrap { |
678ffc93 | 135 | Bootstrap.instance ??= new Bootstrap() |
c4a89082 JB |
136 | return Bootstrap.instance |
137 | } | |
138 | ||
139 | public async addChargingStation ( | |
140 | index: number, | |
141 | templateFile: string, | |
142 | options?: ChargingStationOptions | |
143 | ): Promise<ChargingStationInfo | undefined> { | |
144 | if (!this.started && !this.starting) { | |
145 | throw new BaseError( | |
146 | 'Cannot add charging station while the charging stations simulator is not started' | |
147 | ) | |
148 | } | |
149 | const stationInfo = await this.workerImplementation?.addElement({ | |
150 | index, | |
151 | options, | |
152 | templateFile: join( | |
153 | dirname(fileURLToPath(import.meta.url)), | |
154 | 'assets', | |
155 | 'station-templates', | |
156 | templateFile | |
157 | ), | |
158 | }) | |
159 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
160 | const templateStatistics = this.templateStatistics.get(buildTemplateName(templateFile))! | |
161 | ++templateStatistics.added | |
162 | templateStatistics.indexes.add(index) | |
163 | return stationInfo | |
164 | } | |
165 | ||
166 | public getLastIndex (templateName: string): number { | |
167 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
168 | const indexes = [...this.templateStatistics.get(templateName)!.indexes] | |
169 | .concat(0) | |
170 | .sort((a, b) => a - b) | |
171 | for (let i = 0; i < indexes.length - 1; i++) { | |
172 | if (indexes[i + 1] - indexes[i] !== 1) { | |
173 | return indexes[i] | |
174 | } | |
175 | } | |
176 | return indexes[indexes.length - 1] | |
177 | } | |
178 | ||
179 | public getPerformanceStatistics (): IterableIterator<Statistics> | undefined { | |
180 | return this.storage?.getPerformanceStatistics() | |
181 | } | |
182 | ||
183 | public getState (): SimulatorState { | |
184 | return { | |
185 | configuration: Configuration.getConfigurationData(), | |
186 | started: this.started, | |
187 | templateStatistics: this.templateStatistics, | |
188 | version: this.version, | |
189 | } | |
190 | } | |
191 | ||
192 | public async start (): Promise<void> { | |
193 | if (!this.started) { | |
194 | if (!this.starting) { | |
195 | this.starting = true | |
196 | this.on(ChargingStationWorkerMessageEvents.added, this.workerEventAdded) | |
197 | this.on(ChargingStationWorkerMessageEvents.deleted, this.workerEventDeleted) | |
198 | this.on(ChargingStationWorkerMessageEvents.started, this.workerEventStarted) | |
199 | this.on(ChargingStationWorkerMessageEvents.stopped, this.workerEventStopped) | |
200 | this.on(ChargingStationWorkerMessageEvents.updated, this.workerEventUpdated) | |
201 | this.on( | |
202 | ChargingStationWorkerMessageEvents.performanceStatistics, | |
203 | this.workerEventPerformanceStatistics | |
204 | ) | |
205 | // eslint-disable-next-line @typescript-eslint/unbound-method | |
206 | if (isAsyncFunction(this.workerImplementation?.start)) { | |
207 | await this.workerImplementation.start() | |
208 | } else { | |
209 | ;(this.workerImplementation?.start as () => void)() | |
210 | } | |
211 | const performanceStorageConfiguration = | |
212 | Configuration.getConfigurationSection<StorageConfiguration>( | |
213 | ConfigurationSection.performanceStorage | |
214 | ) | |
215 | if (performanceStorageConfiguration.enabled === true) { | |
216 | this.storage = StorageFactory.getStorage( | |
217 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
218 | performanceStorageConfiguration.type!, | |
219 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
220 | performanceStorageConfiguration.uri!, | |
221 | this.logPrefix() | |
222 | ) | |
223 | await this.storage?.open() | |
224 | } | |
225 | if ( | |
226 | !this.uiServerStarted && | |
227 | Configuration.getConfigurationSection<UIServerConfiguration>( | |
228 | ConfigurationSection.uiServer | |
229 | ).enabled === true | |
230 | ) { | |
231 | this.uiServer.start() | |
232 | this.uiServerStarted = true | |
233 | } | |
234 | // Start ChargingStation object instance in worker thread | |
235 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
236 | for (const stationTemplateUrl of Configuration.getStationTemplateUrls()!) { | |
237 | try { | |
238 | const nbStations = stationTemplateUrl.numberOfStations | |
239 | for (let index = 1; index <= nbStations; index++) { | |
240 | await this.addChargingStation(index, stationTemplateUrl.file) | |
241 | } | |
242 | } catch (error) { | |
243 | console.error( | |
244 | chalk.red( | |
245 | `Error at starting charging station with template file ${stationTemplateUrl.file}: ` | |
246 | ), | |
247 | error | |
248 | ) | |
249 | } | |
250 | } | |
251 | const workerConfiguration = Configuration.getConfigurationSection<WorkerConfiguration>( | |
252 | ConfigurationSection.worker | |
253 | ) | |
254 | console.info( | |
255 | chalk.green( | |
256 | `Charging stations simulator ${this.version} started with ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s) from ${this.numberOfChargingStationTemplates.toString()} charging station template(s) and ${ | |
257 | Configuration.workerDynamicPoolInUse() | |
258 | ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
259 | `${workerConfiguration.poolMinSize?.toString()}/` | |
260 | : '' | |
261 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
262 | }${this.workerImplementation?.size.toString()}${ | |
263 | Configuration.workerPoolInUse() | |
264 | ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
265 | `/${workerConfiguration.poolMaxSize?.toString()}` | |
266 | : '' | |
267 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
268 | } worker(s) concurrently running in '${workerConfiguration.processType}' mode${ | |
269 | this.workerImplementation?.maxElementsPerWorker != null | |
270 | ? ` (${this.workerImplementation.maxElementsPerWorker.toString()} charging station(s) per worker)` | |
271 | : '' | |
272 | }` | |
273 | ) | |
274 | ) | |
275 | Configuration.workerDynamicPoolInUse() && | |
276 | console.warn( | |
277 | chalk.yellow( | |
278 | 'Charging stations simulator is using dynamic pool mode. This is an experimental feature with known issues.\nPlease consider using fixed pool or worker set mode instead' | |
279 | ) | |
280 | ) | |
281 | console.info(chalk.green('Worker set/pool information:'), this.workerImplementation?.info) | |
282 | this.started = true | |
283 | this.starting = false | |
284 | } else { | |
285 | console.error(chalk.red('Cannot start an already starting charging stations simulator')) | |
286 | } | |
287 | } else { | |
288 | console.error(chalk.red('Cannot start an already started charging stations simulator')) | |
289 | } | |
290 | } | |
291 | ||
292 | public async stop (): Promise<void> { | |
293 | if (this.started) { | |
294 | if (!this.stopping) { | |
295 | this.stopping = true | |
296 | await this.uiServer.sendInternalRequest( | |
297 | this.uiServer.buildProtocolRequest( | |
298 | generateUUID(), | |
299 | ProcedureName.STOP_CHARGING_STATION, | |
300 | Constants.EMPTY_FROZEN_OBJECT | |
301 | ) | |
302 | ) | |
303 | try { | |
304 | await this.waitChargingStationsStopped() | |
305 | } catch (error) { | |
306 | console.error(chalk.red('Error while waiting for charging stations to stop: '), error) | |
307 | } | |
308 | await this.workerImplementation?.stop() | |
309 | this.removeAllListeners() | |
310 | this.uiServer.clearCaches() | |
311 | await this.storage?.close() | |
312 | delete this.storage | |
313 | this.started = false | |
314 | this.stopping = false | |
315 | } else { | |
316 | console.error(chalk.red('Cannot stop an already stopping charging stations simulator')) | |
317 | } | |
318 | } else { | |
319 | console.error(chalk.red('Cannot stop an already stopped charging stations simulator')) | |
320 | } | |
ded13d97 JB |
321 | } |
322 | ||
0749233f JB |
323 | private gracefulShutdown (): void { |
324 | this.stop() | |
325 | .then(() => { | |
326 | console.info(chalk.green('Graceful shutdown')) | |
327 | this.uiServer.stop() | |
328 | this.uiServerStarted = false | |
329 | this.waitChargingStationsStopped() | |
330 | // eslint-disable-next-line promise/no-nesting | |
331 | .then(() => { | |
332 | return exit(exitCodes.succeeded) | |
333 | }) | |
334 | // eslint-disable-next-line promise/no-nesting | |
335 | .catch(() => { | |
336 | exit(exitCodes.gracefulShutdownError) | |
337 | }) | |
338 | return undefined | |
339 | }) | |
340 | .catch((error: unknown) => { | |
341 | console.error(chalk.red('Error while shutdowning charging stations simulator: '), error) | |
342 | exit(exitCodes.gracefulShutdownError) | |
343 | }) | |
240fa4da JB |
344 | } |
345 | ||
0749233f | 346 | private initializeCounters (): void { |
e375708d | 347 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
0749233f JB |
348 | const stationTemplateUrls = Configuration.getStationTemplateUrls()! |
349 | if (isNotEmptyArray(stationTemplateUrls)) { | |
350 | for (const stationTemplateUrl of stationTemplateUrls) { | |
351 | const templateName = buildTemplateName(stationTemplateUrl.file) | |
352 | this.templateStatistics.set(templateName, { | |
353 | added: 0, | |
354 | configured: stationTemplateUrl.numberOfStations, | |
355 | indexes: new Set<number>(), | |
356 | provisioned: stationTemplateUrl.provisionedNumberOfStations ?? 0, | |
357 | started: 0, | |
358 | }) | |
359 | this.uiServer.chargingStationTemplates.add(templateName) | |
360 | } | |
361 | if (this.templateStatistics.size !== stationTemplateUrls.length) { | |
362 | console.error( | |
363 | chalk.red( | |
364 | "'stationTemplateUrls' contains duplicate entries, please check your configuration" | |
365 | ) | |
366 | ) | |
367 | exit(exitCodes.duplicateChargingStationTemplateUrls) | |
e375708d | 368 | } |
0749233f JB |
369 | } else { |
370 | console.error( | |
371 | chalk.red("'stationTemplateUrls' not defined or empty, please check your configuration") | |
372 | ) | |
373 | exit(exitCodes.missingChargingStationsConfiguration) | |
374 | } | |
375 | if ( | |
376 | this.numberOfConfiguredChargingStations === 0 && | |
377 | Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer) | |
378 | .enabled !== true | |
379 | ) { | |
380 | console.error( | |
381 | chalk.red( | |
382 | "'stationTemplateUrls' has no charging station enabled and UI server is disabled, please check your configuration" | |
383 | ) | |
384 | ) | |
385 | exit(exitCodes.noChargingStationTemplates) | |
e375708d | 386 | } |
244c1396 JB |
387 | } |
388 | ||
0749233f JB |
389 | private initializeWorkerImplementation (workerConfiguration: WorkerConfiguration): void { |
390 | if (!isMainThread) { | |
391 | return | |
392 | } | |
393 | let elementsPerWorker: number | |
394 | switch (workerConfiguration.elementsPerWorker) { | |
395 | case 'all': | |
396 | elementsPerWorker = | |
397 | this.numberOfConfiguredChargingStations + this.numberOfProvisionedChargingStations | |
398 | break | |
399 | case 'auto': | |
400 | elementsPerWorker = | |
401 | this.numberOfConfiguredChargingStations + this.numberOfProvisionedChargingStations > | |
402 | availableParallelism() | |
403 | ? Math.round( | |
404 | (this.numberOfConfiguredChargingStations + | |
405 | this.numberOfProvisionedChargingStations) / | |
406 | (availableParallelism() * 1.5) | |
407 | ) | |
408 | : 1 | |
409 | break | |
410 | default: | |
411 | elementsPerWorker = workerConfiguration.elementsPerWorker ?? DEFAULT_ELEMENTS_PER_WORKER | |
412 | } | |
413 | this.workerImplementation = WorkerFactory.getWorkerImplementation< | |
414 | ChargingStationWorkerData, | |
415 | ChargingStationInfo | |
416 | >( | |
417 | join( | |
418 | dirname(fileURLToPath(import.meta.url)), | |
419 | `ChargingStationWorker${extname(fileURLToPath(import.meta.url))}` | |
420 | ), | |
421 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
422 | workerConfiguration.processType!, | |
423 | { | |
424 | elementAddDelay: workerConfiguration.elementAddDelay, | |
425 | elementsPerWorker, | |
426 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
427 | poolMaxSize: workerConfiguration.poolMaxSize!, | |
428 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
429 | poolMinSize: workerConfiguration.poolMinSize!, | |
430 | poolOptions: { | |
431 | messageHandler: this.messageHandler.bind(this) as MessageHandler<Worker>, | |
432 | ...(workerConfiguration.resourceLimits != null && { | |
433 | workerOptions: { | |
434 | resourceLimits: workerConfiguration.resourceLimits, | |
435 | }, | |
436 | }), | |
437 | }, | |
438 | workerStartDelay: workerConfiguration.startDelay, | |
439 | } | |
2f989136 JB |
440 | ) |
441 | } | |
442 | ||
c4a89082 JB |
443 | private readonly logPrefix = (): string => { |
444 | return logPrefix(' Bootstrap |') | |
445 | } | |
446 | ||
0749233f JB |
447 | private messageHandler ( |
448 | msg: ChargingStationWorkerMessage<ChargingStationWorkerMessageData> | |
449 | ): void { | |
450 | // logger.debug( | |
451 | // `${this.logPrefix()} ${moduleName}.messageHandler: Charging station worker message received: ${JSON.stringify( | |
452 | // msg, | |
453 | // undefined, | |
454 | // 2 | |
455 | // )}` | |
456 | // ) | |
457 | // Skip worker message events processing | |
458 | // eslint-disable-next-line @typescript-eslint/dot-notation | |
459 | if (msg['uuid'] != null) { | |
460 | return | |
461 | } | |
462 | const { data, event } = msg | |
463 | try { | |
464 | switch (event) { | |
465 | case ChargingStationWorkerMessageEvents.added: | |
466 | this.emit(ChargingStationWorkerMessageEvents.added, data) | |
467 | break | |
468 | case ChargingStationWorkerMessageEvents.deleted: | |
469 | this.emit(ChargingStationWorkerMessageEvents.deleted, data) | |
470 | break | |
471 | case ChargingStationWorkerMessageEvents.performanceStatistics: | |
472 | this.emit(ChargingStationWorkerMessageEvents.performanceStatistics, data) | |
473 | break | |
474 | case ChargingStationWorkerMessageEvents.started: | |
475 | this.emit(ChargingStationWorkerMessageEvents.started, data) | |
476 | break | |
477 | case ChargingStationWorkerMessageEvents.stopped: | |
478 | this.emit(ChargingStationWorkerMessageEvents.stopped, data) | |
479 | break | |
480 | case ChargingStationWorkerMessageEvents.updated: | |
481 | this.emit(ChargingStationWorkerMessageEvents.updated, data) | |
482 | break | |
483 | default: | |
484 | throw new BaseError( | |
485 | `Unknown charging station worker message event: '${event}' received with data: ${JSON.stringify( | |
486 | data, | |
487 | undefined, | |
488 | 2 | |
489 | )}` | |
490 | ) | |
491 | } | |
492 | } catch (error) { | |
493 | logger.error( | |
494 | `${this.logPrefix()} ${moduleName}.messageHandler: Error occurred while handling charging station worker message event '${event}':`, | |
495 | error | |
496 | ) | |
497 | } | |
498 | } | |
499 | ||
500 | private async restart (): Promise<void> { | |
501 | await this.stop() | |
502 | if ( | |
503 | this.uiServerStarted && | |
504 | Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer) | |
505 | .enabled !== true | |
506 | ) { | |
507 | this.uiServer.stop() | |
508 | this.uiServerStarted = false | |
509 | } | |
510 | this.initializeCounters() | |
511 | // FIXME: initialize worker implementation only if the worker section has changed | |
512 | this.initializeWorkerImplementation( | |
513 | Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker) | |
514 | ) | |
515 | await this.start() | |
516 | } | |
517 | ||
518 | private async waitChargingStationsStopped (): Promise<string> { | |
519 | return await new Promise<string>((resolve, reject: (reason?: unknown) => void) => { | |
520 | const waitTimeout = setTimeout(() => { | |
521 | const timeoutMessage = `Timeout ${formatDurationMilliSeconds( | |
522 | Constants.STOP_CHARGING_STATIONS_TIMEOUT | |
523 | )} reached at stopping charging stations` | |
524 | console.warn(chalk.yellow(timeoutMessage)) | |
525 | reject(new Error(timeoutMessage)) | |
526 | }, Constants.STOP_CHARGING_STATIONS_TIMEOUT) | |
527 | waitChargingStationEvents( | |
528 | this, | |
529 | ChargingStationWorkerMessageEvents.stopped, | |
530 | this.numberOfStartedChargingStations | |
531 | ) | |
532 | .then(events => { | |
533 | resolve('Charging stations stopped') | |
534 | return events | |
535 | }) | |
536 | .finally(() => { | |
537 | clearTimeout(waitTimeout) | |
538 | }) | |
539 | .catch(reject) | |
540 | }) | |
541 | } | |
542 | ||
c4a89082 JB |
543 | private readonly workerEventAdded = (data: ChargingStationData): void => { |
544 | this.uiServer.chargingStations.set(data.stationInfo.hashId, data) | |
545 | logger.info( | |
546 | `${this.logPrefix()} ${moduleName}.workerEventAdded: Charging station ${ | |
547 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
548 | data.stationInfo.chargingStationId | |
549 | } (hashId: ${data.stationInfo.hashId}) added (${this.numberOfAddedChargingStations.toString()} added from ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s))` | |
550 | ) | |
0749233f JB |
551 | } |
552 | ||
c4a89082 JB |
553 | private readonly workerEventDeleted = (data: ChargingStationData): void => { |
554 | this.uiServer.chargingStations.delete(data.stationInfo.hashId) | |
0749233f | 555 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
c4a89082 JB |
556 | const templateStatistics = this.templateStatistics.get(data.stationInfo.templateName)! |
557 | --templateStatistics.added | |
558 | templateStatistics.indexes.delete(data.stationInfo.templateIndex) | |
559 | logger.info( | |
560 | `${this.logPrefix()} ${moduleName}.workerEventDeleted: Charging station ${ | |
561 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
562 | data.stationInfo.chargingStationId | |
563 | } (hashId: ${data.stationInfo.hashId}) deleted (${this.numberOfAddedChargingStations.toString()} added from ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s))` | |
564 | ) | |
ded13d97 JB |
565 | } |
566 | ||
c4a89082 JB |
567 | private readonly workerEventPerformanceStatistics = (data: Statistics): void => { |
568 | // eslint-disable-next-line @typescript-eslint/unbound-method | |
569 | if (isAsyncFunction(this.storage?.storePerformanceStatistics)) { | |
570 | ;( | |
571 | this.storage.storePerformanceStatistics as ( | |
572 | performanceStatistics: Statistics | |
573 | ) => Promise<void> | |
574 | )(data).catch(Constants.EMPTY_FUNCTION) | |
b322b8b4 | 575 | } else { |
c4a89082 JB |
576 | ;(this.storage?.storePerformanceStatistics as (performanceStatistics: Statistics) => void)( |
577 | data | |
578 | ) | |
ded13d97 | 579 | } |
ded13d97 JB |
580 | } |
581 | ||
c4a89082 JB |
582 | private readonly workerEventStarted = (data: ChargingStationData): void => { |
583 | this.uiServer.chargingStations.set(data.stationInfo.hashId, data) | |
584 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
585 | ++this.templateStatistics.get(data.stationInfo.templateName)!.started | |
586 | logger.info( | |
587 | `${this.logPrefix()} ${moduleName}.workerEventStarted: Charging station ${ | |
588 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
589 | data.stationInfo.chargingStationId | |
590 | } (hashId: ${data.stationInfo.hashId}) started (${this.numberOfStartedChargingStations.toString()} started from ${this.numberOfAddedChargingStations.toString()} added charging station(s))` | |
244c1396 JB |
591 | ) |
592 | } | |
593 | ||
c4a89082 JB |
594 | private readonly workerEventStopped = (data: ChargingStationData): void => { |
595 | this.uiServer.chargingStations.set(data.stationInfo.hashId, data) | |
596 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
597 | --this.templateStatistics.get(data.stationInfo.templateName)!.started | |
598 | logger.info( | |
599 | `${this.logPrefix()} ${moduleName}.workerEventStopped: Charging station ${ | |
600 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
601 | data.stationInfo.chargingStationId | |
602 | } (hashId: ${data.stationInfo.hashId}) stopped (${this.numberOfStartedChargingStations.toString()} started from ${this.numberOfAddedChargingStations.toString()} added charging station(s))` | |
66a7748d JB |
603 | ) |
604 | } | |
32de5a57 | 605 | |
c4a89082 JB |
606 | private readonly workerEventUpdated = (data: ChargingStationData): void => { |
607 | this.uiServer.chargingStations.set(data.stationInfo.hashId, data) | |
66a7748d | 608 | } |
ded13d97 | 609 | } |