Ensure 1:1 mapping between charging station instance and its OCPP services
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 99e8f5ee6aaf91b923c8da8d491e4ba383381e12..028de5918c9f38cc7d9dd4281b589611d19b19a4 100644 (file)
@@ -42,6 +42,7 @@ import { parentPort } from 'worker_threads';
 import path from 'path';
 
 export default class ChargingStation {
+  public readonly id: string;
   public readonly stationTemplateFile: string;
   public authorizedTags: string[];
   public stationInfo!: ChargingStationInfo;
@@ -66,18 +67,16 @@ export default class ChargingStation {
   private webSocketPingSetInterval!: NodeJS.Timeout;
 
   constructor(index: number, stationTemplateFile: string) {
+    this.id = Utils.generateUUID();
     this.index = index;
     this.stationTemplateFile = stationTemplateFile;
-    this.connectors = new Map<number, ConnectorStatus>();
-    this.initialize();
-
     this.stopped = false;
     this.wsConnectionRestarted = false;
     this.autoReconnectRetryCount = 0;
-
+    this.connectors = new Map<number, ConnectorStatus>();
     this.requests = new Map<string, CachedRequest>();
     this.messageBuffer = new Set<string>();
-
+    this.initialize();
     this.authorizedTags = this.getAuthorizedTags();
   }
 
@@ -123,6 +122,14 @@ export default class ChargingStation {
     return this?.wsConnection?.readyState === OPEN;
   }
 
+  public getRegistrationStatus(): RegistrationStatus {
+    return this?.bootNotificationResponse?.status;
+  }
+
+  public isInUnknownState(): boolean {
+    return Utils.isNullOrUndefined(this?.bootNotificationResponse?.status);
+  }
+
   public isInPendingState(): boolean {
     return this?.bootNotificationResponse?.status === RegistrationStatus.PENDING;
   }
@@ -136,7 +143,7 @@ export default class ChargingStation {
   }
 
   public isRegistered(): boolean {
-    return this.isInAcceptedState() || this.isInPendingState();
+    return !this.isInUnknownState() && (this.isInAcceptedState() || this.isInPendingState());
   }
 
   public isChargingStationAvailable(): boolean {
@@ -144,7 +151,7 @@ export default class ChargingStation {
   }
 
   public isConnectorAvailable(id: number): boolean {
-    return this.getConnectorStatus(id).availability === AvailabilityType.OPERATIVE;
+    return id > 0 && this.getConnectorStatus(id).availability === AvailabilityType.OPERATIVE;
   }
 
   public getNumberOfConnectors(): number {
@@ -159,6 +166,10 @@ export default class ChargingStation {
     return this.stationInfo.currentOutType ?? CurrentType.AC;
   }
 
+  public getOcppStrictCompliance(): boolean {
+    return this.stationInfo.ocppStrictCompliance ?? false;
+  }
+
   public getVoltageOut(): number | undefined {
     const errMsg = `${this.logPrefix()} Unknown ${this.getCurrentOutType()} currentOutType in template file ${this.stationTemplateFile}, cannot define default voltage out`;
     let defaultVoltageOut: number;
@@ -577,8 +588,8 @@ export default class ChargingStation {
     this.wsConfiguredConnectionUrl = new URL(this.getConfiguredSupervisionUrl().href + '/' + this.stationInfo.chargingStationId);
     switch (this.getOcppVersion()) {
       case OCPPVersion.VERSION_16:
-        this.ocppIncomingRequestService = new OCPP16IncomingRequestService(this);
-        this.ocppRequestService = new OCPP16RequestService(this, new OCPP16ResponseService(this));
+        this.ocppIncomingRequestService = OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>(this);
+        this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(this, OCPP16ResponseService.getInstance<OCPP16ResponseService>(this));
         break;
       default:
         this.handleUnsupportedVersion(this.getOcppVersion());
@@ -595,7 +606,7 @@ export default class ChargingStation {
     }
     this.stationInfo.powerDivider = this.getPowerDivider();
     if (this.getEnableStatistics()) {
-      this.performanceStatistics = new PerformanceStatistics(this.stationInfo.chargingStationId, this.wsConnectionUrl);
+      this.performanceStatistics = PerformanceStatistics.getInstance(this.id, this.stationInfo.chargingStationId, this.wsConnectionUrl);
     }
   }
 
@@ -641,21 +652,17 @@ export default class ChargingStation {
 
   private async onOpen(): Promise<void> {
     logger.info(`${this.logPrefix()} Connected to OCPP server through ${this.wsConnectionUrl.toString()}`);
-    if (!this.isRegistered()) {
+    if (!this.isInAcceptedState()) {
       // Send BootNotification
       let registrationRetryCount = 0;
       do {
         this.bootNotificationResponse = await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
           this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
-        if (!this.isRegistered()) {
-          registrationRetryCount++;
+        if (!this.isInAcceptedState()) {
+          this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
           await Utils.sleep(this.bootNotificationResponse?.interval ? this.bootNotificationResponse.interval * 1000 : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL);
         }
-      } while (!this.isRegistered() && (registrationRetryCount <= this.getRegistrationMaxRetries() || this.getRegistrationMaxRetries() === -1));
-    }
-    if (this.isRegistered() && this.stationInfo.autoRegister) {
-      await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
-        this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
+      } while (!this.isInAcceptedState() && (registrationRetryCount <= this.getRegistrationMaxRetries() || this.getRegistrationMaxRetries() === -1));
     }
     if (this.isInAcceptedState()) {
       await this.startMessageSequence();
@@ -663,16 +670,6 @@ export default class ChargingStation {
       if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) {
         this.flushMessageBuffer();
       }
-    } else if (this.isInPendingState()) {
-      // The central server shall issue a TriggerMessage to the charging station for the boot notification at the end of its configuration process
-      while (!this.isInAcceptedState()) {
-        await Utils.sleep(Constants.CHARGING_STATION_DEFAULT_START_SEQUENCE_DELAY);
-      }
-      await this.startMessageSequence();
-      this.stopped && (this.stopped = false);
-      if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) {
-        this.flushMessageBuffer();
-      }
     } else {
       logger.error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`);
     }
@@ -876,6 +873,10 @@ export default class ChargingStation {
   }
 
   private async startMessageSequence(): Promise<void> {
+    if (this.stationInfo.autoRegister) {
+      await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
+        this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
+    }
     // Start WebSocket ping
     this.startWebSocketPing();
     // Start heartbeat
@@ -923,8 +924,7 @@ export default class ChargingStation {
     this.stopHeartbeat();
     // Stop the ATG
     if (this.stationInfo.AutomaticTransactionGenerator.enable &&
-      this.automaticTransactionGenerator &&
-      this.automaticTransactionGenerator.started) {
+      this.automaticTransactionGenerator?.started) {
       this.automaticTransactionGenerator.stop();
     } else {
       for (const connectorId of this.connectors.keys()) {
@@ -1115,8 +1115,7 @@ export default class ChargingStation {
     // Stop the ATG if needed
     if (this.stationInfo.AutomaticTransactionGenerator.enable &&
       this.stationInfo.AutomaticTransactionGenerator.stopOnConnectionFailure &&
-      this.automaticTransactionGenerator &&
-      this.automaticTransactionGenerator.started) {
+      this.automaticTransactionGenerator?.started) {
       this.automaticTransactionGenerator.stop();
     }
     if (this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries() || this.getAutoReconnectMaxRetries() === -1) {