Add Hearbeat command to OCPP 2.0.1
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 689fe2d98099f844a42b1d15195def868f55746b..84aa81ca649336eda64779744c87600ac5d9a098 100644 (file)
@@ -43,6 +43,8 @@ import {
   type BootNotificationRequest,
   type CachedRequest,
   type ErrorCallback,
+  FirmwareStatus,
+  type FirmwareStatusNotificationRequest,
   type HeartbeatRequest,
   type IncomingRequest,
   IncomingRequestCommand,
@@ -54,9 +56,10 @@ import {
 import {
   type BootNotificationResponse,
   type ErrorResponse,
+  type FirmwareStatusNotificationResponse,
   type HeartbeatResponse,
   type MeterValuesResponse,
-  RegistrationStatus,
+  RegistrationStatusEnumType,
   type Response,
   type StatusNotificationResponse,
 } from '../types/ocpp/Responses';
@@ -170,17 +173,15 @@ export default class ChargingStation {
     );
   }
 
-  public getEnableStatistics(): boolean | undefined {
-    return !Utils.isUndefined(this.stationInfo.enableStatistics)
-      ? this.stationInfo.enableStatistics
-      : true;
+  public getEnableStatistics(): boolean {
+    return this.stationInfo.enableStatistics ?? false;
   }
 
-  public getMustAuthorizeAtRemoteStart(): boolean | undefined {
+  public getMustAuthorizeAtRemoteStart(): boolean {
     return this.stationInfo.mustAuthorizeAtRemoteStart ?? true;
   }
 
-  public getPayloadSchemaValidation(): boolean | undefined {
+  public getPayloadSchemaValidation(): boolean {
     return this.stationInfo.payloadSchemaValidation ?? true;
   }
 
@@ -200,7 +201,7 @@ export default class ChargingStation {
     return this?.wsConnection?.readyState === WebSocket.OPEN;
   }
 
-  public getRegistrationStatus(): RegistrationStatus {
+  public getRegistrationStatus(): RegistrationStatusEnumType {
     return this?.bootNotificationResponse?.status;
   }
 
@@ -209,15 +210,15 @@ export default class ChargingStation {
   }
 
   public isInPendingState(): boolean {
-    return this?.bootNotificationResponse?.status === RegistrationStatus.PENDING;
+    return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING;
   }
 
   public isInAcceptedState(): boolean {
-    return this?.bootNotificationResponse?.status === RegistrationStatus.ACCEPTED;
+    return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED;
   }
 
   public isInRejectedState(): boolean {
-    return this?.bootNotificationResponse?.status === RegistrationStatus.REJECTED;
+    return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED;
   }
 
   public isRegistered(): boolean {
@@ -484,7 +485,7 @@ export default class ChargingStation {
     if (this.started === false) {
       if (this.starting === false) {
         this.starting = true;
-        if (this.getEnableStatistics()) {
+        if (this.getEnableStatistics() === true) {
           this.performanceStatistics.start();
         }
         this.openWSConnection();
@@ -512,7 +513,7 @@ export default class ChargingStation {
                 ) {
                   this.startAutomaticTransactionGenerator();
                 }
-                if (this.getEnableStatistics()) {
+                if (this.getEnableStatistics() === true) {
                   this.performanceStatistics.restart();
                 } else {
                   this.performanceStatistics.stop();
@@ -544,7 +545,7 @@ export default class ChargingStation {
         this.stopping = true;
         await this.stopMessageSequence(reason);
         this.closeWSConnection();
-        if (this.getEnableStatistics()) {
+        if (this.getEnableStatistics() === true) {
           this.performanceStatistics.stop();
         }
         this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
@@ -824,21 +825,6 @@ export default class ChargingStation {
       'supervisionUrl',
       'supervisionUrls'
     );
-    const firmwareVersionRegExp = stationTemplate.firmwareVersionPattern
-      ? new RegExp(stationTemplate.firmwareVersionPattern)
-      : Constants.SEMVER_REGEXP;
-    if (
-      stationTemplate.firmwareVersion &&
-      firmwareVersionRegExp.test(stationTemplate.firmwareVersion) === false
-    ) {
-      logger.warn(
-        `${this.logPrefix()} Firmware version '${
-          stationTemplate.firmwareVersion
-        }' in template file ${
-          this.templateFile
-        } does not match regular expression '${firmwareVersionRegExp.toString()}'`
-      );
-    }
     const stationInfo: ChargingStationInfo =
       ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
     stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate);
@@ -861,6 +847,18 @@ export default class ChargingStation {
           ? stationTemplate.power * 1000
           : stationTemplate.power;
     }
+    stationInfo.firmwareVersionPattern =
+      stationTemplate.firmwareVersionPattern ?? Constants.SEMVER_PATTERN;
+    if (
+      stationInfo.firmwareVersion &&
+      new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion) === false
+    ) {
+      logger.warn(
+        `${this.logPrefix()} Firmware version '${stationInfo.firmwareVersion}' in template file ${
+          this.templateFile
+        } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
+      );
+    }
     stationInfo.resetTime = stationTemplate.resetTime
       ? stationTemplate.resetTime * 1000
       : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
@@ -958,7 +956,7 @@ export default class ChargingStation {
     // Avoid duplication of connectors related information in RAM
     this.stationInfo?.Connectors && delete this.stationInfo.Connectors;
     this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
-    if (this.getEnableStatistics()) {
+    if (this.getEnableStatistics() === true) {
       this.performanceStatistics = PerformanceStatistics.getInstance(
         this.stationInfo.hashId,
         this.stationInfo.chargingStationId,
@@ -994,11 +992,23 @@ export default class ChargingStation {
     }
     if (this.stationInfo?.autoRegister === true) {
       this.bootNotificationResponse = {
-        currentTime: new Date().toISOString(),
+        currentTime: new Date(),
         interval: this.getHeartbeatInterval() / 1000,
-        status: RegistrationStatus.ACCEPTED,
+        status: RegistrationStatusEnumType.ACCEPTED,
       };
     }
+    if (
+      this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
+      this.stationInfo.firmwareVersion &&
+      this.stationInfo.firmwareVersionPattern
+    ) {
+      const match = this.stationInfo.firmwareVersion
+        .match(new RegExp(this.stationInfo.firmwareVersionPattern))
+        .slice(1, this.stationInfo.firmwareVersion.split('.').length + 1);
+      const patchLevelIndex = match.length - 1;
+      match[patchLevelIndex] = (Utils.convertToInt(match[patchLevelIndex]) + 1).toString();
+      this.stationInfo.firmwareVersion = match.join('.');
+    }
   }
 
   private initializeOcppConfiguration(): void {
@@ -1780,7 +1790,7 @@ export default class ChargingStation {
           logger.error(
             `${this.logPrefix()} Charging profile id ${
               matchingChargingProfile.chargingProfileId
-            } limit is greater than connector id ${connectorId} maximum, dump charging profiles' stack: %j`,
+            } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}, dump charging profiles' stack: %j`,
             this.getConnectorStatus(connectorId).chargingProfiles
           );
           limit = connectorMaximumPower;
@@ -1837,6 +1847,16 @@ export default class ChargingStation {
       });
       this.getConnectorStatus(connectorId).status = chargePointStatus;
     }
+    if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
+      await this.ocppRequestService.requestHandler<
+        FirmwareStatusNotificationRequest,
+        FirmwareStatusNotificationResponse
+      >(this, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+        status: FirmwareStatus.Installed,
+      });
+      this.stationInfo.firmwareStatus = FirmwareStatus.Installed;
+    }
+
     // Start the ATG
     if (this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true) {
       this.startAutomaticTransactionGenerator();