docs(ui): refine ui/web/README.md wording
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 9da4fdbb41934219468867ed824b94fd3fb36cd3..4ee9ff6783e2aecfbdb148e4a855bad1b602254c 100644 (file)
@@ -8,7 +8,7 @@ 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'
@@ -88,7 +88,6 @@ import {
   FirmwareStatus,
   type FirmwareStatusNotificationRequest,
   type FirmwareStatusNotificationResponse,
-  type FirmwareUpgrade,
   type HeartbeatRequest,
   type HeartbeatResponse,
   type IncomingRequest,
@@ -162,13 +161,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
@@ -185,7 +184,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
@@ -253,11 +252,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()
     }
@@ -540,6 +534,15 @@ export class ChargingStation extends EventEmitter {
     return Constants.DEFAULT_HEARTBEAT_INTERVAL
   }
 
+  public setSupervisionUrls (urls: string | string[], saveStationInfo = true): void {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.stationInfo!.supervisionUrls = urls
+    if (saveStationInfo) {
+      this.saveStationInfo()
+    }
+    this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
+  }
+
   public setSupervisionUrl (url: string): void {
     if (
       this.stationInfo?.supervisionUrlOcppConfiguration === true &&
@@ -547,10 +550,7 @@ export class ChargingStation extends EventEmitter {
     ) {
       setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey, url)
     } else {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.stationInfo!.supervisionUrls = url
-      this.saveStationInfo()
-      this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
+      this.setSupervisionUrls(url)
     }
   }
 
@@ -1038,15 +1038,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
@@ -1169,6 +1168,7 @@ export class ChargingStation extends EventEmitter {
     }
     const stationInfo = stationTemplateToStationInfo(stationTemplate)
     stationInfo.hashId = getHashId(this.index, stationTemplate)
+    stationInfo.templateIndex = this.index
     stationInfo.templateName = parse(this.templateFile).name
     stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate)
     createSerialNumber(stationTemplate, stationInfo)
@@ -1198,7 +1198,7 @@ export class ChargingStation extends EventEmitter {
         } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
       )
     }
-    stationInfo.firmwareUpgrade = merge<FirmwareUpgrade>(
+    stationInfo.firmwareUpgrade = mergeDeepRight(
       {
         versionUpgrade: {
           step: 1
@@ -1222,6 +1222,11 @@ 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
@@ -1246,6 +1251,7 @@ export class ChargingStation extends EventEmitter {
       stationInfoFromFile.templateHash === stationInfoFromTemplate.templateHash
     ) {
       return setChargingStationOptions(
+        this,
         { ...Constants.DEFAULT_STATION_INFO, ...stationInfoFromFile },
         options
       )
@@ -1257,6 +1263,7 @@ export class ChargingStation extends EventEmitter {
         stationInfoFromTemplate
       )
     return setChargingStationOptions(
+      this,
       { ...Constants.DEFAULT_STATION_INFO, ...stationInfoFromTemplate },
       options
     )
@@ -1723,7 +1730,7 @@ export class ChargingStation extends EventEmitter {
         } else {
           delete configurationData.configurationKey
         }
-        configurationData = merge<ChargingStationConfiguration>(
+        configurationData = mergeDeepRight(
           configurationData,
           buildChargingStationAutomaticTransactionGeneratorConfiguration(this)
         )
@@ -1825,6 +1832,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`
       )
@@ -1887,6 +1895,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:
@@ -1906,12 +1915,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 (