fix: fix template name consistency
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 4ef9ab5d92e5d1f80307f44314d3e5bbb3cbc894..f79a59cea3943c2c9a79ca7c6cde8b37582b3d85 100644 (file)
@@ -2,13 +2,13 @@
 
 import { createHash } from 'node:crypto'
 import { EventEmitter } from 'node:events'
-import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
-import { dirname, join, parse } from 'node:path'
+import { type FSWatcher, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import { dirname, join } from 'node:path'
 import { URL } from 'node:url'
 import { parentPort } from 'node:worker_threads'
 
 import { millisecondsToSeconds, secondsToMilliseconds } from 'date-fns'
-import merge from 'just-merge'
+import { mergeDeepRight } from 'rambda'
 import { type RawData, WebSocket } from 'ws'
 
 import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator.js'
@@ -42,6 +42,7 @@ import {
   hasReservationExpired,
   initializeConnectorsMapStatus,
   propagateSerialNumber,
+  setChargingStationOptions,
   stationTemplateToStationInfo,
   warnTemplateKeysDeprecation
 } from './Helpers.js'
@@ -87,7 +88,6 @@ import {
   FirmwareStatus,
   type FirmwareStatusNotificationRequest,
   type FirmwareStatusNotificationResponse,
-  type FirmwareUpgrade,
   type HeartbeatRequest,
   type HeartbeatResponse,
   type IncomingRequest,
@@ -127,9 +127,11 @@ import {
   buildAddedMessage,
   buildChargingStationAutomaticTransactionGeneratorConfiguration,
   buildConnectorsStatus,
+  buildDeletedMessage,
   buildEvsesStatus,
   buildStartedMessage,
   buildStoppedMessage,
+  buildTemplateName,
   buildUpdatedMessage,
   clone,
   convertToBoolean,
@@ -160,13 +162,13 @@ export class ChargingStation extends EventEmitter {
   public started: boolean
   public starting: boolean
   public idTagsCache: IdTagsCache
-  public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined
-  public ocppConfiguration!: ChargingStationOcppConfiguration | undefined
+  public automaticTransactionGenerator?: AutomaticTransactionGenerator
+  public ocppConfiguration?: ChargingStationOcppConfiguration
   public wsConnection: WebSocket | null
   public readonly connectors: Map<number, ConnectorStatus>
   public readonly evses: Map<number, EvseStatus>
   public readonly requests: Map<string, CachedRequest>
-  public performanceStatistics!: PerformanceStatistics | undefined
+  public performanceStatistics?: PerformanceStatistics
   public heartbeatSetInterval?: NodeJS.Timeout
   public ocppRequestService!: OCPPRequestService
   public bootNotificationRequest?: BootNotificationRequest
@@ -183,7 +185,7 @@ export class ChargingStation extends EventEmitter {
   private configuredSupervisionUrl!: URL
   private wsConnectionRetried: boolean
   private wsConnectionRetryCount: number
-  private templateFileWatcher!: FSWatcher | undefined
+  private templateFileWatcher?: FSWatcher
   private templateFileHash!: string
   private readonly sharedLRUCache: SharedLRUCache
   private wsPingSetInterval?: NodeJS.Timeout
@@ -211,6 +213,9 @@ export class ChargingStation extends EventEmitter {
     this.on(ChargingStationEvents.added, () => {
       parentPort?.postMessage(buildAddedMessage(this))
     })
+    this.on(ChargingStationEvents.deleted, () => {
+      parentPort?.postMessage(buildDeletedMessage(this))
+    })
     this.on(ChargingStationEvents.started, () => {
       parentPort?.postMessage(buildStartedMessage(this))
     })
@@ -248,11 +253,6 @@ export class ChargingStation extends EventEmitter {
 
     this.add()
 
-    if (options?.autoStart != null) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.stationInfo!.autoStart = options.autoStart
-    }
-
     if (this.stationInfo?.autoStart === true) {
       this.start()
     }
@@ -544,8 +544,8 @@ export class ChargingStation extends EventEmitter {
     } else {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       this.stationInfo!.supervisionUrls = url
-      this.saveStationInfo()
       this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
+      this.saveStationInfo()
     }
   }
 
@@ -666,6 +666,24 @@ export class ChargingStation extends EventEmitter {
     this.emit(ChargingStationEvents.added)
   }
 
+  public async delete (deleteConfiguration = true): Promise<void> {
+    if (this.started) {
+      await this.stop()
+    }
+    AutomaticTransactionGenerator.deleteInstance(this)
+    PerformanceStatistics.deleteInstance(this.stationInfo?.hashId)
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!)
+    this.requests.clear()
+    this.connectors.clear()
+    this.evses.clear()
+    this.templateFileWatcher?.unref()
+    deleteConfiguration && rmSync(this.configurationFile, { force: true })
+    this.chargingStationWorkerBroadcastChannel.unref()
+    this.emit(ChargingStationEvents.deleted)
+    this.removeAllListeners()
+  }
+
   public start (): void {
     if (!this.started) {
       if (!this.starting) {
@@ -689,10 +707,10 @@ export class ChargingStation extends EventEmitter {
                   } file have changed, reload`
                 )
                 this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash)
-                // Initialize
-                this.initialize()
                 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                 this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!)
+                // Initialize
+                this.initialize()
                 // Restart the ATG
                 const ATGStarted = this.automaticTransactionGenerator?.started
                 if (ATGStarted === true) {
@@ -743,12 +761,11 @@ export class ChargingStation extends EventEmitter {
         if (this.stationInfo?.enableStatistics === true) {
           this.performanceStatistics?.stop()
         }
-        this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
         this.templateFileWatcher?.close()
-        this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash)
         delete this.bootNotificationResponse
         this.started = false
         this.saveConfiguration()
+        this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
         this.emit(ChargingStationEvents.stopped)
         this.stopping = false
       } else {
@@ -1016,15 +1033,14 @@ export class ChargingStation extends EventEmitter {
     connectorId?: number
   ): boolean {
     const reservation = this.getReservationBy('reservationId', reservationId)
-    const reservationExists = reservation !== undefined && !hasReservationExpired(reservation)
+    const reservationExists = reservation != null && !hasReservationExpired(reservation)
     if (arguments.length === 1) {
       return !reservationExists
     } else if (arguments.length > 1) {
-      const userReservation =
-        idTag !== undefined ? this.getReservationBy('idTag', idTag) : undefined
+      const userReservation = idTag != null ? this.getReservationBy('idTag', idTag) : undefined
       const userReservationExists =
-        userReservation !== undefined && !hasReservationExpired(userReservation)
-      const notConnectorZero = connectorId === undefined ? true : connectorId > 0
+        userReservation != null && !hasReservationExpired(userReservation)
+      const notConnectorZero = connectorId == null ? true : connectorId > 0
       const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0
       return (
         !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable
@@ -1147,7 +1163,8 @@ export class ChargingStation extends EventEmitter {
     }
     const stationInfo = stationTemplateToStationInfo(stationTemplate)
     stationInfo.hashId = getHashId(this.index, stationTemplate)
-    stationInfo.templateName = parse(this.templateFile).name
+    stationInfo.templateIndex = this.index
+    stationInfo.templateName = buildTemplateName(this.templateFile)
     stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate)
     createSerialNumber(stationTemplate, stationInfo)
     stationInfo.voltageOut = this.getVoltageOut(stationInfo)
@@ -1176,7 +1193,7 @@ export class ChargingStation extends EventEmitter {
         } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
       )
     }
-    stationInfo.firmwareUpgrade = merge<FirmwareUpgrade>(
+    stationInfo.firmwareUpgrade = mergeDeepRight(
       {
         versionUpgrade: {
           step: 1
@@ -1200,20 +1217,24 @@ export class ChargingStation extends EventEmitter {
       stationInfo = this.getConfigurationFromFile()?.stationInfo
       if (stationInfo != null) {
         delete stationInfo.infoHash
+        delete (stationInfo as ChargingStationTemplate).numberOfConnectors
+        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+        if (stationInfo.templateIndex == null) {
+          stationInfo.templateIndex = this.index
+        }
         // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
         if (stationInfo.templateName == null) {
-          stationInfo.templateName = parse(this.templateFile).name
+          stationInfo.templateName = buildTemplateName(this.templateFile)
         }
       }
     }
     return stationInfo
   }
 
-  private getStationInfo (stationInfoPersistentConfiguration?: boolean): ChargingStationInfo {
+  private getStationInfo (options?: ChargingStationOptions): ChargingStationInfo {
     const stationInfoFromTemplate = this.getStationInfoFromTemplate()
-    stationInfoPersistentConfiguration != null &&
-      (stationInfoFromTemplate.stationInfoPersistentConfiguration =
-        stationInfoPersistentConfiguration)
+    options?.persistentConfiguration != null &&
+      (stationInfoFromTemplate.stationInfoPersistentConfiguration = options.persistentConfiguration)
     const stationInfoFromFile = this.getStationInfoFromFile(
       stationInfoFromTemplate.stationInfoPersistentConfiguration
     )
@@ -1224,7 +1245,10 @@ export class ChargingStation extends EventEmitter {
       stationInfoFromFile != null &&
       stationInfoFromFile.templateHash === stationInfoFromTemplate.templateHash
     ) {
-      return { ...Constants.DEFAULT_STATION_INFO, ...stationInfoFromFile }
+      return setChargingStationOptions(
+        { ...Constants.DEFAULT_STATION_INFO, ...stationInfoFromFile },
+        options
+      )
     }
     stationInfoFromFile != null &&
       propagateSerialNumber(
@@ -1232,7 +1256,10 @@ export class ChargingStation extends EventEmitter {
         stationInfoFromFile,
         stationInfoFromTemplate
       )
-    return { ...Constants.DEFAULT_STATION_INFO, ...stationInfoFromTemplate }
+    return setChargingStationOptions(
+      { ...Constants.DEFAULT_STATION_INFO, ...stationInfoFromTemplate },
+      options
+    )
   }
 
   private saveStationInfo (): void {
@@ -1265,24 +1292,7 @@ export class ChargingStation extends EventEmitter {
     } else {
       this.initializeConnectorsOrEvsesFromTemplate(stationTemplate)
     }
-    this.stationInfo = this.getStationInfo(options?.persistentConfiguration)
-    if (options?.persistentConfiguration != null) {
-      this.stationInfo.ocppPersistentConfiguration = options.persistentConfiguration
-      this.stationInfo.automaticTransactionGeneratorPersistentConfiguration =
-        options.persistentConfiguration
-    }
-    if (options?.autoRegister != null) {
-      this.stationInfo.autoRegister = options.autoRegister
-    }
-    if (options?.enableStatistics != null) {
-      this.stationInfo.enableStatistics = options.enableStatistics
-    }
-    if (options?.ocppStrictCompliance != null) {
-      this.stationInfo.ocppStrictCompliance = options.ocppStrictCompliance
-    }
-    if (options?.stopTransactionsOnStopped != null) {
-      this.stationInfo.stopTransactionsOnStopped = options.stopTransactionsOnStopped
-    }
+    this.stationInfo = this.getStationInfo(options)
     if (
       this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
       isNotEmptyString(this.stationInfo.firmwareVersionPattern) &&
@@ -1713,7 +1723,7 @@ export class ChargingStation extends EventEmitter {
         } else {
           delete configurationData.configurationKey
         }
-        configurationData = merge<ChargingStationConfiguration>(
+        configurationData = mergeDeepRight(
           configurationData,
           buildChargingStationAutomaticTransactionGeneratorConfiguration(this)
         )
@@ -1815,6 +1825,7 @@ export class ChargingStation extends EventEmitter {
 
   private async onOpen (): Promise<void> {
     if (this.isWebSocketConnectionOpened()) {
+      this.emit(ChargingStationEvents.updated)
       logger.info(
         `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} succeeded`
       )
@@ -1877,6 +1888,7 @@ export class ChargingStation extends EventEmitter {
 
   private onClose (code: WebSocketCloseEventStatusCode, reason: Buffer): void {
     this.emit(ChargingStationEvents.disconnected)
+    this.emit(ChargingStationEvents.updated)
     switch (code) {
       // Normal close
       case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
@@ -1896,12 +1908,13 @@ export class ChargingStation extends EventEmitter {
           )}' and reason '${reason.toString()}'`
         )
         this.started &&
-          this.reconnect().catch(error =>
-            logger.error(`${this.logPrefix()} Error while reconnecting:`, error)
-          )
+          this.reconnect()
+            .then(() => {
+              this.emit(ChargingStationEvents.updated)
+            })
+            .catch(error => logger.error(`${this.logPrefix()} Error while reconnecting:`, error))
         break
     }
-    this.emit(ChargingStationEvents.updated)
   }
 
   private getCachedRequest (