feat: add `deleteChargingStations` SRPC command to UI Services
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index aca295df61a0fae9f3bbef46f720fac49db444d8..2b5b5f8376fca34c915965ab1b0d46df590ef6ad 100644 (file)
@@ -2,7 +2,7 @@
 
 import { createHash } from 'node:crypto'
 import { EventEmitter } from 'node:events'
-import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
+import { type FSWatcher, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
 import { dirname, join, parse } from 'node:path'
 import { URL } from 'node:url'
 import { parentPort } from 'node:worker_threads'
@@ -127,6 +127,7 @@ import {
   buildAddedMessage,
   buildChargingStationAutomaticTransactionGeneratorConfiguration,
   buildConnectorsStatus,
+  buildDeletedMessage,
   buildEvsesStatus,
   buildStartedMessage,
   buildStoppedMessage,
@@ -166,7 +167,7 @@ export class ChargingStation extends EventEmitter {
   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 | undefined
   public heartbeatSetInterval?: NodeJS.Timeout
   public ocppRequestService!: OCPPRequestService
   public bootNotificationRequest?: BootNotificationRequest
@@ -183,7 +184,7 @@ export class ChargingStation extends EventEmitter {
   private configuredSupervisionUrl!: URL
   private wsConnectionRetried: boolean
   private wsConnectionRetryCount: number
-  private templateFileWatcher!: FSWatcher | undefined
+  private templateFileWatcher: FSWatcher | undefined
   private templateFileHash!: string
   private readonly sharedLRUCache: SharedLRUCache
   private wsPingSetInterval?: NodeJS.Timeout
@@ -211,6 +212,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))
     })
@@ -666,6 +670,23 @@ 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)
+  }
+
   public start (): void {
     if (!this.started) {
       if (!this.starting) {
@@ -689,10 +710,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 +764,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 {
@@ -1147,10 +1167,8 @@ export class ChargingStation extends EventEmitter {
     }
     const stationInfo = stationTemplateToStationInfo(stationTemplate)
     stationInfo.hashId = getHashId(this.index, stationTemplate)
-    stationInfo.autoStart = stationTemplate.autoStart ?? true
     stationInfo.templateName = parse(this.templateFile).name
     stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate)
-    stationInfo.ocppVersion = stationTemplate.ocppVersion ?? OCPPVersion.VERSION_16
     createSerialNumber(stationTemplate, stationInfo)
     stationInfo.voltageOut = this.getVoltageOut(stationInfo)
     if (isNotEmptyArray(stationTemplate.power)) {
@@ -1167,9 +1185,8 @@ export class ChargingStation extends EventEmitter {
           : stationTemplate.power
     }
     stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo)
-    stationInfo.firmwareVersionPattern =
-      stationTemplate.firmwareVersionPattern ?? Constants.SEMVER_PATTERN
     if (
+      isNotEmptyString(stationInfo.firmwareVersionPattern) &&
       isNotEmptyString(stationInfo.firmwareVersion) &&
       !new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion)
     ) {
@@ -1188,15 +1205,15 @@ export class ChargingStation extends EventEmitter {
       },
       stationTemplate.firmwareUpgrade ?? {}
     )
-    stationInfo.resetTime =
-      stationTemplate.resetTime != null
-        ? secondsToMilliseconds(stationTemplate.resetTime)
-        : Constants.DEFAULT_CHARGING_STATION_RESET_TIME
+    if (stationTemplate.resetTime != null) {
+      stationInfo.resetTime = secondsToMilliseconds(stationTemplate.resetTime)
+    }
     return stationInfo
   }
 
   private getStationInfoFromFile (
-    stationInfoPersistentConfiguration?: boolean
+    stationInfoPersistentConfiguration: boolean | undefined = Constants.DEFAULT_STATION_INFO
+      .stationInfoPersistentConfiguration
   ): ChargingStationInfo | undefined {
     let stationInfo: ChargingStationInfo | undefined
     if (stationInfoPersistentConfiguration === true) {
@@ -1207,9 +1224,6 @@ export class ChargingStation extends EventEmitter {
         if (stationInfo.templateName == null) {
           stationInfo.templateName = parse(this.templateFile).name
         }
-        if (stationInfo.autoStart == null) {
-          stationInfo.autoStart = true
-        }
       }
     }
     return stationInfo
@@ -1221,7 +1235,7 @@ export class ChargingStation extends EventEmitter {
       (stationInfoFromTemplate.stationInfoPersistentConfiguration =
         stationInfoPersistentConfiguration)
     const stationInfoFromFile = this.getStationInfoFromFile(
-      stationInfoFromTemplate.stationInfoPersistentConfiguration ?? true
+      stationInfoFromTemplate.stationInfoPersistentConfiguration
     )
     // Priority:
     // 1. charging station info from template
@@ -1274,18 +1288,25 @@ export class ChargingStation extends EventEmitter {
     this.stationInfo = this.getStationInfo(options?.persistentConfiguration)
     if (options?.persistentConfiguration != null) {
       this.stationInfo.ocppPersistentConfiguration = options.persistentConfiguration
-    }
-    if (options?.persistentConfiguration != null) {
       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
+    }
     if (
       this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
-      isNotEmptyString(this.stationInfo.firmwareVersion) &&
-      isNotEmptyString(this.stationInfo.firmwareVersionPattern)
+      isNotEmptyString(this.stationInfo.firmwareVersionPattern) &&
+      isNotEmptyString(this.stationInfo.firmwareVersion)
     ) {
       const patternGroup =
         this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
@@ -2058,7 +2079,7 @@ export class ChargingStation extends EventEmitter {
       if (!(error instanceof OCPPError)) {
         logger.warn(
           `${this.logPrefix()} Error thrown at incoming OCPP command '${
-            commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+            commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
             // eslint-disable-next-line @typescript-eslint/no-base-to-string
           }' message '${data.toString()}' handling is not an OCPPError:`,
           error
@@ -2066,7 +2087,7 @@ export class ChargingStation extends EventEmitter {
       }
       logger.error(
         `${this.logPrefix()} Incoming OCPP command '${
-          commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+          commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
           // eslint-disable-next-line @typescript-eslint/no-base-to-string
         }' message '${data.toString()}'${
           this.requests.has(messageId)
@@ -2114,7 +2135,8 @@ export class ChargingStation extends EventEmitter {
   }
 
   private getUseConnectorId0 (stationTemplate?: ChargingStationTemplate): boolean {
-    return stationTemplate?.useConnectorId0 ?? true
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return stationTemplate?.useConnectorId0 ?? Constants.DEFAULT_STATION_INFO.useConnectorId0!
   }
 
   private async stopRunningTransactions (reason?: StopTransactionReason): Promise<void> {
@@ -2173,8 +2195,12 @@ export class ChargingStation extends EventEmitter {
   }
 
   private getCurrentOutType (stationInfo?: ChargingStationInfo): CurrentType {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return (stationInfo ?? this.stationInfo!).currentOutType ?? CurrentType.AC
+    return (
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      (stationInfo ?? this.stationInfo!).currentOutType ??
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      Constants.DEFAULT_STATION_INFO.currentOutType!
+    )
   }
 
   private getVoltageOut (stationInfo?: ChargingStationInfo): Voltage {