Fix request and response handling in all registration state
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 00cfce9f96f00014a251284a02494abe9be7d5bc..6be15b83e980656b237a7a1586078134dd17ae54 100644 (file)
@@ -5,7 +5,6 @@ import { BootNotificationResponse, RegistrationStatus } from '../types/ocpp/Resp
 import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration';
 import ChargingStationTemplate, { CurrentType, PowerUnits, Voltage } from '../types/ChargingStationTemplate';
 import { ConnectorPhaseRotation, StandardParametersKey, SupportedFeatureProfiles, VendorDefaultParametersKey } from '../types/ocpp/Configuration';
-import { ConnectorStatus, SampledValueTemplate } from '../types/Connectors';
 import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues';
 import { WSError, WebSocketCloseEventStatusCode } from '../types/WebSocket';
 import WebSocket, { ClientOptions, Data, OPEN } from 'ws';
@@ -17,6 +16,7 @@ import ChargingStationInfo from '../types/ChargingStationInfo';
 import { ChargingStationWorkerMessageEvents } from '../types/ChargingStationWorker';
 import { ClientRequestArgs } from 'http';
 import Configuration from '../utils/Configuration';
+import { ConnectorStatus } from '../types/ConnectorStatus';
 import Constants from '../utils/Constants';
 import { ErrorType } from '../types/ocpp/ErrorType';
 import FileUtils from '../utils/FileUtils';
@@ -29,7 +29,9 @@ import OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
 import OCPPRequestService from './ocpp/OCPPRequestService';
 import { OCPPVersion } from '../types/ocpp/OCPPVersion';
 import PerformanceStatistics from '../performance/PerformanceStatistics';
+import { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates';
 import { StopTransactionReason } from '../types/ocpp/Transaction';
+import { SupervisionUrlDistribution } from '../types/ConfigurationData';
 import { URL } from 'url';
 import Utils from '../utils/Utils';
 import crypto from 'crypto';
@@ -120,10 +122,22 @@ export default class ChargingStation {
     return this?.wsConnection?.readyState === OPEN;
   }
 
-  public isRegistered(): boolean {
+  public isInPendingState(): boolean {
+    return this?.bootNotificationResponse?.status === RegistrationStatus.PENDING;
+  }
+
+  public isInAcceptedState(): boolean {
     return this?.bootNotificationResponse?.status === RegistrationStatus.ACCEPTED;
   }
 
+  public isInRejectedState(): boolean {
+    return this?.bootNotificationResponse?.status === RegistrationStatus.REJECTED;
+  }
+
+  public isRegistered(): boolean {
+    return this.isInAcceptedState() || this.isInPendingState();
+  }
+
   public isChargingStationAvailable(): boolean {
     return this.getConnectorStatus(0).availability === AvailabilityType.OPERATIVE;
   }
@@ -460,6 +474,10 @@ export default class ChargingStation {
     } catch (error) {
       FileUtils.handleFileException(this.logPrefix(), 'Template', this.stationTemplateFile, error as NodeJS.ErrnoException);
     }
+    const chargingStationId = this.getChargingStationId(stationTemplateFromFile);
+    // Deprecation template keys section
+    this.warnDeprecatedTemplateKey(stationTemplateFromFile, 'supervisionUrl', chargingStationId, 'Use \'supervisionUrls\' instead');
+    this.convertDeprecatedTemplateKey(stationTemplateFromFile, 'supervisionUrl', 'supervisionUrls');
     const stationInfo: ChargingStationInfo = stationTemplateFromFile ?? {} as ChargingStationInfo;
     stationInfo.wsOptions = stationTemplateFromFile?.wsOptions ?? {};
     if (!Utils.isEmptyArray(stationTemplateFromFile.power)) {
@@ -476,7 +494,7 @@ export default class ChargingStation {
     }
     delete stationInfo.power;
     delete stationInfo.powerUnit;
-    stationInfo.chargingStationId = this.getChargingStationId(stationTemplateFromFile);
+    stationInfo.chargingStationId = chargingStationId;
     stationInfo.resetTime = stationTemplateFromFile.resetTime ? stationTemplateFromFile.resetTime * 1000 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
     return stationInfo;
   }
@@ -638,7 +656,17 @@ export default class ChargingStation {
       await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
         this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
     }
-    if (this.isRegistered()) {
+    if (this.isInAcceptedState()) {
+      await this.startMessageSequence();
+      this.stopped && (this.stopped = false);
+      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()) {
@@ -932,17 +960,45 @@ export default class ChargingStation {
     }
   }
 
+  private warnDeprecatedTemplateKey(template: ChargingStationTemplate, key: string, chargingStationId: string, logMsgToAppend = ''): void {
+    if (!Utils.isUndefined(template[key])) {
+      logger.warn(`${Utils.logPrefix(` ${chargingStationId} |`)} Deprecated template key '${key}' usage in file '${this.stationTemplateFile}'${logMsgToAppend && '. ' + logMsgToAppend}`);
+    }
+  }
+
+  private convertDeprecatedTemplateKey(template: ChargingStationTemplate, deprecatedKey: string, key: string): void {
+    if (!Utils.isUndefined(template[deprecatedKey])) {
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+      template[key] = template[deprecatedKey];
+      delete template[deprecatedKey];
+    }
+  }
+
   private getConfiguredSupervisionUrl(): URL {
-    const supervisionUrls = Utils.cloneObject<string | string[]>(this.stationInfo.supervisionUrl ?? Configuration.getSupervisionUrls());
-    let indexUrl = 0;
+    const supervisionUrls = Utils.cloneObject<string | string[]>(this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls());
     if (!Utils.isEmptyArray(supervisionUrls)) {
-      if (Configuration.getDistributeStationsToTenantsEqually()) {
-        indexUrl = this.index % supervisionUrls.length;
-      } else {
-        // Get a random url
-        indexUrl = Math.floor(Utils.secureRandom() * supervisionUrls.length);
+      let urlIndex = 0;
+      switch (Configuration.getSupervisionUrlDistribution()) {
+        case SupervisionUrlDistribution.ROUND_ROBIN:
+          urlIndex = (this.index - 1) % supervisionUrls.length;
+          break;
+        case SupervisionUrlDistribution.RANDOM:
+          // Get a random url
+          urlIndex = Math.floor(Utils.secureRandom() * supervisionUrls.length);
+          break;
+        case SupervisionUrlDistribution.SEQUENTIAL:
+          if (this.index <= supervisionUrls.length) {
+            urlIndex = this.index - 1;
+          } else {
+            logger.warn(`${this.logPrefix()} No more configured supervision urls available, using the first one`);
+          }
+          break;
+        default:
+          logger.error(`${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${SupervisionUrlDistribution.ROUND_ROBIN}`);
+          urlIndex = (this.index - 1) % supervisionUrls.length;
+          break;
       }
-      return new URL(supervisionUrls[indexUrl]);
+      return new URL(supervisionUrls[urlIndex]);
     }
     return new URL(supervisionUrls as string);
   }