From edf4bd64342698102411d17e433377a9c6eb860d Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 25 Jan 2021 17:16:16 +0100 Subject: [PATCH] Add ClearChargingProfile OCPP command support. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/ChargingStation.ts | 87 +++++++++++++++++++++---- src/types/ocpp/1.6/RequestResponses.ts | 9 +++ src/types/ocpp/1.6/Requests.ts | 11 +++- src/utils/Constants.ts | 12 ++-- 4 files changed, 100 insertions(+), 19 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index f646a32c..2456defb 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -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); diff --git a/src/types/ocpp/1.6/RequestResponses.ts b/src/types/ocpp/1.6/RequestResponses.ts index a9aa8761..e8861ec9 100644 --- a/src/types/ocpp/1.6/RequestResponses.ts +++ b/src/types/ocpp/1.6/RequestResponses.ts @@ -73,3 +73,12 @@ export enum AvailabilityStatus { export interface ChangeAvailabilityResponse { status: AvailabilityStatus; } + +export enum ClearChargingProfileStatus { + ACCEPTED = 'Accepted', + UNKNOWN = 'Unknown' +} + +export interface ClearChargingProfileResponse { + status: ClearChargingProfileStatus; +} diff --git a/src/types/ocpp/1.6/Requests.ts b/src/types/ocpp/1.6/Requests.ts index e7e66223..0602c283 100644 --- a/src/types/ocpp/1.6/Requests.ts +++ b/src/types/ocpp/1.6/Requests.ts @@ -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; +} diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 3e002e60..c36ade65 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -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'; -- 2.34.1