Add ClearChargingProfile OCPP command support.
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 25 Jan 2021 16:16:16 +0000 (17:16 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 25 Jan 2021 16:16:16 +0000 (17:16 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ChargingStation.ts
src/types/ocpp/1.6/RequestResponses.ts
src/types/ocpp/1.6/Requests.ts
src/utils/Constants.ts

index f646a32c092240f3b3d84df64b6bce709819795b..2456defb31f9556efefdf9ad21326d76f7c47553 100644 (file)
@@ -1,6 +1,6 @@
 import { AuthorizationStatus, AuthorizeRequest, AuthorizeResponse, StartTransactionRequest, StartTransactionResponse, StopTransactionReason, StopTransactionRequest, StopTransactionResponse } from '../types/ocpp/1.6/Transaction';
-import { AvailabilityType, BootNotificationRequest, ChangeAvailabilityRequest, ChangeConfigurationRequest, GetConfigurationRequest, HeartbeatRequest, IncomingRequestCommand, RemoteStartTransactionRequest, RemoteStopTransactionRequest, RequestCommand, ResetRequest, SetChargingProfileRequest, StatusNotificationRequest, UnlockConnectorRequest } from '../types/ocpp/1.6/Requests';
-import { BootNotificationResponse, ChangeAvailabilityResponse, ChangeConfigurationResponse, DefaultResponse, GetConfigurationResponse, HeartbeatResponse, RegistrationStatus, SetChargingProfileResponse, StatusNotificationResponse, UnlockConnectorResponse } from '../types/ocpp/1.6/RequestResponses';
+import { AvailabilityType, BootNotificationRequest, ChangeAvailabilityRequest, ChangeConfigurationRequest, ClearChargingProfileRequest, GetConfigurationRequest, HeartbeatRequest, IncomingRequestCommand, RemoteStartTransactionRequest, RemoteStopTransactionRequest, RequestCommand, ResetRequest, SetChargingProfileRequest, StatusNotificationRequest, UnlockConnectorRequest } from '../types/ocpp/1.6/Requests';
+import { BootNotificationResponse, ChangeAvailabilityResponse, ChangeConfigurationResponse, ClearChargingProfileResponse, DefaultResponse, GetConfigurationResponse, HeartbeatResponse, RegistrationStatus, SetChargingProfileResponse, StatusNotificationResponse, UnlockConnectorResponse } from '../types/ocpp/1.6/RequestResponses';
 import { ChargingProfile, ChargingProfilePurposeType } from '../types/ocpp/1.6/ChargingProfile';
 import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration';
 import ChargingStationTemplate, { PowerOutType, VoltageOut } from '../types/ChargingStationTemplate';
@@ -1239,23 +1239,74 @@ export default class ChargingStation {
     }
   }
 
+  _setChargingProfile(connectorId: number, cp: ChargingProfile): boolean {
+    if (!Utils.isEmptyArray(this.getConnector(connectorId).chargingProfiles)) {
+      this.getConnector(connectorId).chargingProfiles.forEach((chargingProfile: ChargingProfile, index: number) => {
+        if (chargingProfile.chargingProfileId === cp.chargingProfileId
+          || (chargingProfile.stackLevel === cp.stackLevel && chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)) {
+          this.getConnector(connectorId).chargingProfiles[index] = cp;
+          return true;
+        }
+      });
+    }
+    this.getConnector(connectorId).chargingProfiles.push(cp);
+    return true;
+  }
+
   handleRequestSetChargingProfile(commandPayload: SetChargingProfileRequest): SetChargingProfileResponse {
     if (!this.getConnector(commandPayload.connectorId)) {
       logger.error(`${this._logPrefix()} Trying to set a charging profile to a non existing connector Id ${commandPayload.connectorId}`);
-      return Constants.OCPP_CHARGING_PROFILE_RESPONSE_REJECTED;
+      return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
+    }
+    if (commandPayload.csChargingProfiles.chargingProfilePurpose === ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE && commandPayload.connectorId !== 0) {
+      return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
+    }
+    if (commandPayload.csChargingProfiles.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE && (commandPayload.connectorId === 0 || !this.getConnector(commandPayload.connectorId)?.transactionStarted)) {
+      return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
     }
-    if (commandPayload.csChargingProfiles.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE && !this.getConnector(commandPayload.connectorId)?.transactionStarted) {
-      return Constants.OCPP_CHARGING_PROFILE_RESPONSE_REJECTED;
+    this._setChargingProfile(commandPayload.connectorId, commandPayload.csChargingProfiles);
+    return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
+  }
+
+  handleRequestClearChargingProfile(commandPayload: ClearChargingProfileRequest): ClearChargingProfileResponse {
+    if (!this.getConnector(commandPayload.connectorId)) {
+      logger.error(`${this._logPrefix()} Trying to clear a charging profile to a non existing connector Id ${commandPayload.connectorId}`);
+      return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
+    }
+    if (commandPayload.connectorId && !Utils.isEmptyArray(this.getConnector(commandPayload.connectorId).chargingProfiles)) {
+      this.getConnector(commandPayload.connectorId).chargingProfiles = [];
+      return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
     }
-    this.getConnector(commandPayload.connectorId).chargingProfiles.forEach((chargingProfile: ChargingProfile, index: number) => {
-      if (chargingProfile.chargingProfileId === commandPayload.csChargingProfiles.chargingProfileId
-        || (chargingProfile.stackLevel === commandPayload.csChargingProfiles.stackLevel && chargingProfile.chargingProfilePurpose === commandPayload.csChargingProfiles.chargingProfilePurpose)) {
-        this.getConnector(commandPayload.connectorId).chargingProfiles[index] = chargingProfile;
-        return Constants.OCPP_CHARGING_PROFILE_RESPONSE_ACCEPTED;
+    if (!commandPayload.connectorId) {
+      let clearedCP = false;
+      for (const connector in this.connectors) {
+        if (!Utils.isEmptyArray(this.getConnector(Utils.convertToInt(connector)).chargingProfiles)) {
+          this.getConnector(Utils.convertToInt(connector)).chargingProfiles.forEach((chargingProfile: ChargingProfile, index: number) => {
+            let clearCurrentCP = false;
+            if (chargingProfile.chargingProfileId === commandPayload.id) {
+              clearCurrentCP = true;
+            }
+            if (!commandPayload.chargingProfilePurpose && chargingProfile.stackLevel === commandPayload.stackLevel) {
+              clearCurrentCP = true;
+            }
+            if (!chargingProfile.stackLevel && chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose) {
+              clearCurrentCP = true;
+            }
+            if (chargingProfile.stackLevel === commandPayload.stackLevel && chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose) {
+              clearCurrentCP = true;
+            }
+            if (clearCurrentCP) {
+              this.getConnector(commandPayload.connectorId).chargingProfiles[index] = {} as ChargingProfile;
+              clearedCP = true;
+            }
+          });
+        }
       }
-    });
-    this.getConnector(commandPayload.connectorId).chargingProfiles.push(commandPayload.csChargingProfiles);
-    return Constants.OCPP_CHARGING_PROFILE_RESPONSE_ACCEPTED;
+      if (clearedCP) {
+        return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
+      }
+    }
+    return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
   }
 
   handleRequestChangeAvailability(commandPayload: ChangeAvailabilityRequest): ChangeAvailabilityResponse {
@@ -1294,6 +1345,11 @@ export default class ChargingStation {
         // Check if authorized
         if (this.authorizedTags.find((value) => value === commandPayload.idTag)) {
           await this.sendStatusNotification(transactionConnectorID, ChargePointStatus.PREPARING);
+          if (commandPayload.chargingProfile && commandPayload.chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
+            this._setChargingProfile(transactionConnectorID, commandPayload.chargingProfile);
+          } else {
+            return Constants.OCPP_RESPONSE_REJECTED;
+          }
           // Authorization successful start transaction
           await this.sendStartTransaction(transactionConnectorID, commandPayload.idTag);
           logger.debug(this._logPrefix() + ' Transaction remotely STARTED on ' + this.stationInfo.chargingStationId + '#' + transactionConnectorID.toString() + ' for idTag ' + commandPayload.idTag);
@@ -1303,6 +1359,11 @@ export default class ChargingStation {
         return Constants.OCPP_RESPONSE_REJECTED;
       }
       await this.sendStatusNotification(transactionConnectorID, ChargePointStatus.PREPARING);
+      if (commandPayload.chargingProfile && commandPayload.chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
+        this._setChargingProfile(transactionConnectorID, commandPayload.chargingProfile);
+      } else {
+        return Constants.OCPP_RESPONSE_REJECTED;
+      }
       // No local authorization check required => start transaction
       await this.sendStartTransaction(transactionConnectorID, commandPayload.idTag);
       logger.debug(this._logPrefix() + ' Transaction remotely STARTED on ' + this.stationInfo.chargingStationId + '#' + transactionConnectorID.toString() + ' for idTag ' + commandPayload.idTag);
index a9aa876134166020f2bbe6097a1ddc139b9024d4..e8861ec946240f3c6fd69cbf17cc758b0ac05056 100644 (file)
@@ -73,3 +73,12 @@ export enum AvailabilityStatus {
 export interface ChangeAvailabilityResponse {
   status: AvailabilityStatus;
 }
+
+export enum ClearChargingProfileStatus {
+  ACCEPTED = 'Accepted',
+  UNKNOWN = 'Unknown'
+}
+
+export interface ClearChargingProfileResponse {
+  status: ClearChargingProfileStatus;
+}
index e7e662233aecb7ff6d4176111fd213aeaad30d37..0602c2831910dbddef90decc2e3f176cc60d3e04 100644 (file)
@@ -1,6 +1,7 @@
+import { ChargingProfile, ChargingProfilePurposeType } from './ChargingProfile';
+
 import { ChargePointErrorCode } from './ChargePointErrorCode';
 import { ChargePointStatus } from './ChargePointStatus';
-import { ChargingProfile } from './ChargingProfile';
 import { StandardParametersKey } from './Configuration';
 
 export enum RequestCommand {
@@ -22,6 +23,7 @@ export enum IncomingRequestCommand {
   GET_CONFIGURATION = 'GetConfiguration',
   CHANGE_CONFIGURATION = 'ChangeConfiguration',
   SET_CHARGING_PROFILE = 'SetChargingProfile',
+  CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
   REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
   REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction'
 }
@@ -97,3 +99,10 @@ export interface ChangeAvailabilityRequest {
   connectorId: number;
   type: AvailabilityType;
 }
+
+export interface ClearChargingProfileRequest {
+  id?: number;
+  connectorId?: number;
+  chargingProfilePurpose?: ChargingProfilePurposeType;
+  stackLevel?: number;
+}
index 3e002e600a7882f99de7fc2e1782d05b2b62c11c..c36ade65e9facfe159ba7bd3a9cc9a8d00ec81c2 100644 (file)
@@ -1,4 +1,4 @@
-import { AvailabilityStatus, ChargingProfileStatus, ConfigurationStatus, DefaultStatus, UnlockStatus } from '../types/ocpp/1.6/RequestResponses';
+import { AvailabilityStatus, ChargingProfileStatus, ClearChargingProfileStatus, ConfigurationStatus, DefaultStatus, UnlockStatus } from '../types/ocpp/1.6/RequestResponses';
 
 export default class Constants {
   static readonly ENTITY_CHARGING_STATION = 'ChargingStation';
@@ -10,15 +10,17 @@ export default class Constants {
   static readonly OCPP_CONFIGURATION_RESPONSE_REJECTED = Object.freeze({ status: ConfigurationStatus.REJECTED });
   static readonly OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED = Object.freeze({ status: ConfigurationStatus.REBOOT_REQUIRED });
   static readonly OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED = Object.freeze({ status: ConfigurationStatus.NOT_SUPPORTED });
-  static readonly OCPP_CHARGING_PROFILE_RESPONSE_ACCEPTED = Object.freeze({ status: ChargingProfileStatus.ACCEPTED });
-  static readonly OCPP_CHARGING_PROFILE_RESPONSE_REJECTED = Object.freeze({ status: ChargingProfileStatus.REJECTED });
-  static readonly OCPP_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED = Object.freeze({ status: ChargingProfileStatus.NOT_SUPPORTED });
+  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED = Object.freeze({ status: ChargingProfileStatus.ACCEPTED });
+  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED = Object.freeze({ status: ChargingProfileStatus.REJECTED });
+  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED = Object.freeze({ status: ChargingProfileStatus.NOT_SUPPORTED });
+  static readonly OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED = Object.freeze({ status: ClearChargingProfileStatus.ACCEPTED });
+  static readonly OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN = Object.freeze({ status: ClearChargingProfileStatus.UNKNOWN });
   static readonly OCPP_RESPONSE_UNLOCKED = Object.freeze({ status: UnlockStatus.UNLOCKED });
   static readonly OCPP_RESPONSE_UNLOCK_FAILED = Object.freeze({ status: UnlockStatus.UNLOCK_FAILED });
   static readonly OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED = Object.freeze({ status: UnlockStatus.NOT_SUPPORTED });
   static readonly OCPP_AVAILABILITY_RESPONSE_ACCEPTED = Object.freeze({ status: AvailabilityStatus.ACCEPTED });
   static readonly OCPP_AVAILABILITY_RESPONSE_REJECTED = Object.freeze({ status: AvailabilityStatus.REJECTED });
-  static readonly OCPP_AVAILABILITY_RESPONSE_SCHEDULED= Object.freeze({ status: AvailabilityStatus.SCHEDULED });
+  static readonly OCPP_AVAILABILITY_RESPONSE_SCHEDULED = Object.freeze({ status: AvailabilityStatus.SCHEDULED });
 
   static readonly OCPP_PROTOCOL_JSON = 'json';
   static readonly OCPP_PROTOCOL_SOAP = 'soap';