feat: add initial support get composite schedule OCPP 1.6 command
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
index 8b23eddd1e0820c118a8b8242aaa31f413fe7353..999e1ceafaf9a703d81bcf2890e69eec728e3c0a 100644 (file)
@@ -23,6 +23,7 @@ import {
   type ClearChargingProfileResponse,
   ErrorType,
   type GenericResponse,
+  GenericStatus,
   type GetConfigurationRequest,
   type GetConfigurationResponse,
   type GetDiagnosticsRequest,
@@ -40,6 +41,7 @@ import {
   OCPP16ChargePointStatus,
   type OCPP16ChargingProfile,
   OCPP16ChargingProfilePurposeType,
+  type OCPP16ChargingSchedule,
   type OCPP16ClearCacheRequest,
   type OCPP16DataTransferRequest,
   type OCPP16DataTransferResponse,
@@ -51,6 +53,8 @@ import {
   OCPP16FirmwareStatus,
   type OCPP16FirmwareStatusNotificationRequest,
   type OCPP16FirmwareStatusNotificationResponse,
+  type OCPP16GetCompositeScheduleRequest,
+  type OCPP16GetCompositeScheduleResponse,
   type OCPP16HeartbeatRequest,
   type OCPP16HeartbeatResponse,
   OCPP16IncomingRequestCommand,
@@ -103,6 +107,10 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
         this.handleRequestChangeConfiguration.bind(this),
       ],
+      [
+        OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
+        this.handleRequestGetCompositeSchedule.bind(this),
+      ],
       [
         OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
         this.handleRequestSetChargingProfile.bind(this),
@@ -177,6 +185,14 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
           'constructor'
         ),
       ],
+      [
+        OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
+        OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
+          '../../../assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
+          moduleName,
+          'constructor'
+        ),
+      ],
       [
         OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
         OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
@@ -568,6 +584,56 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
   }
 
+  private handleRequestGetCompositeSchedule(
+    chargingStation: ChargingStation,
+    commandPayload: OCPP16GetCompositeScheduleRequest
+  ): OCPP16GetCompositeScheduleResponse {
+    if (
+      OCPP16ServiceUtils.checkFeatureProfile(
+        chargingStation,
+        OCPP16SupportedFeatureProfiles.SmartCharging,
+        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
+      ) === false
+    ) {
+      return OCPPConstants.OCPP_RESPONSE_REJECTED;
+    }
+    if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector Id ${
+          commandPayload.connectorId
+        }`
+      );
+      return OCPPConstants.OCPP_RESPONSE_REJECTED;
+    }
+    if (
+      Utils.isEmptyArray(
+        chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles
+      )
+    ) {
+      return OCPPConstants.OCPP_RESPONSE_REJECTED;
+    }
+    const startDate = new Date();
+    const endDate = new Date(startDate.getTime() + commandPayload.duration * 1000);
+    let compositeSchedule: OCPP16ChargingSchedule;
+    for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId)
+      .chargingProfiles) {
+      // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc.
+      if (
+        chargingProfile.chargingSchedule?.startSchedule >= startDate &&
+        chargingProfile.chargingSchedule?.startSchedule <= endDate
+      ) {
+        compositeSchedule = chargingProfile.chargingSchedule;
+        break;
+      }
+    }
+    return {
+      status: GenericStatus.Accepted,
+      scheduleStart: compositeSchedule?.startSchedule,
+      connectorId: commandPayload.connectorId,
+      chargingSchedule: compositeSchedule,
+    };
+  }
+
   private handleRequestClearChargingProfile(
     chargingStation: ChargingStation,
     commandPayload: ClearChargingProfileRequest
@@ -730,18 +796,19 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       });
       const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
       connectorStatus.status = OCPP16ChargePointStatus.Preparing;
-      if (chargingStation.isChargingStationAvailable() === true) {
+      if (
+        chargingStation.isChargingStationAvailable() === true &&
+        chargingStation.isConnectorAvailable(transactionConnectorId) === true
+      ) {
         // Check if authorized
         if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
           let authorized = false;
           if (
             chargingStation.getLocalAuthListEnabled() === true &&
-            chargingStation.hasAuthorizedTags() === true &&
+            chargingStation.hasIdTags() === true &&
             Utils.isNotEmptyString(
-              chargingStation.authorizedTagsCache
-                .getAuthorizedTags(
-                  ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
-                )
+              chargingStation.idTagsCache
+                .getIdTags(ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo))
                 ?.find((idTag) => idTag === commandPayload.idTag)
             )
           ) {
@@ -969,7 +1036,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     const now = Date.now();
     if (retrieveDate?.getTime() <= now) {
       this.runInAsyncScope(
-        this.updateFirmware.bind(this) as (
+        this.updateFirmwareSimulation.bind(this) as (
           this: OCPP16IncomingRequestService,
           ...args: any[]
         ) => Promise<void>,
@@ -978,18 +1045,23 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       ).catch(Constants.EMPTY_FUNCTION);
     } else {
       setTimeout(() => {
-        this.updateFirmware(chargingStation).catch(Constants.EMPTY_FUNCTION);
+        this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION);
       }, retrieveDate?.getTime() - now);
     }
     return OCPPConstants.OCPP_RESPONSE_EMPTY;
   }
 
-  private async updateFirmware(
+  private async updateFirmwareSimulation(
     chargingStation: ChargingStation,
     maxDelay = 30,
     minDelay = 15
   ): Promise<void> {
-    chargingStation.stopAutomaticTransactionGenerator();
+    if (
+      ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) ===
+      false
+    ) {
+      return;
+    }
     for (const connectorId of chargingStation.connectors.keys()) {
       if (
         connectorId > 0 &&
@@ -1037,7 +1109,58 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       status: OCPP16FirmwareStatus.Downloaded,
     });
     chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
-    await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
+    let wasTransactionsStarted = false;
+    let transactionsStarted: boolean;
+    do {
+      let trxCount = 0;
+      for (const connectorId of chargingStation.connectors.keys()) {
+        if (
+          connectorId > 0 &&
+          chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true
+        ) {
+          trxCount++;
+        }
+      }
+      if (trxCount > 0) {
+        const waitTime = 15 * 1000;
+        logger.debug(
+          `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${trxCount} transaction(s) in progress, waiting ${
+            waitTime / 1000
+          } seconds before continuing firmware update simulation`
+        );
+        await Utils.sleep(waitTime);
+        transactionsStarted = true;
+        wasTransactionsStarted = true;
+      } else {
+        for (const connectorId of chargingStation.connectors.keys()) {
+          if (
+            connectorId > 0 &&
+            chargingStation.getConnectorStatus(connectorId)?.status !==
+              OCPP16ChargePointStatus.Unavailable
+          ) {
+            await chargingStation.ocppRequestService.requestHandler<
+              OCPP16StatusNotificationRequest,
+              OCPP16StatusNotificationResponse
+            >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
+              connectorId,
+              status: OCPP16ChargePointStatus.Unavailable,
+              errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
+            });
+            chargingStation.getConnectorStatus(connectorId).status =
+              OCPP16ChargePointStatus.Unavailable;
+          }
+        }
+        transactionsStarted = false;
+      }
+    } while (transactionsStarted);
+    !wasTransactionsStarted &&
+      (await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000));
+    if (
+      ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) ===
+      false
+    ) {
+      return;
+    }
     await chargingStation.ocppRequestService.requestHandler<
       OCPP16FirmwareStatusNotificationRequest,
       OCPP16FirmwareStatusNotificationResponse