]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/blame - src/charging-station/Bootstrap.ts
perf: speed up mean and median computation
[e-mobility-charging-stations-simulator.git] / src / charging-station / Bootstrap.ts
CommitLineData
a19b897d 1// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
b4d34251 2
1ea7f1df 3import type { Worker } from 'node:worker_threads'
0749233f
JB
4
5import chalk from 'chalk'
66a7748d 6import { EventEmitter } from 'node:events'
a33026fe 7import { dirname, extname, join } from 'node:path'
66a7748d
JB
8import process, { exit } from 'node:process'
9import { fileURLToPath } from 'node:url'
c5ecc04d 10import { isMainThread } from 'node:worker_threads'
4c3f6c20 11import { availableParallelism, type MessageHandler } from 'poolifier'
0749233f
JB
12
13import type { AbstractUIServer } from './ui-server/AbstractUIServer.js'
8114d10e 14
66a7748d
JB
15import { version } from '../../package.json'
16import { BaseError } from '../exception/index.js'
17import { type Storage, StorageFactory } from '../performance/index.js'
e7aeea18 18import {
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
35import {
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 47import { DEFAULT_ELEMENTS_PER_WORKER, type WorkerAbstract, WorkerFactory } from '../worker/index.js'
4c3f6c20 48import { buildTemplateName, waitChargingStationEvents } from './Helpers.js'
4c3f6c20 49import { UIServerFactory } from './ui-server/UIServerFactory.js'
ded13d97 50
66a7748d 51const moduleName = 'Bootstrap'
32de5a57 52
c4a89082 53/* eslint-disable perfectionist/sort-enums */
a307349b 54enum 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 63export 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}