From 411894569d4a0333a4e38e911a178520a69448cd Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 27 Mar 2023 20:24:03 +0200 Subject: [PATCH 1/1] feat: add initial support get composite schedule OCPP 1.6 command MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/ChargingStationUtils.ts | 25 +++++-- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 66 +++++++++++++++++++ .../ocpp/1.6/OCPP16ResponseService.ts | 9 +++ src/types/index.ts | 3 + src/types/ocpp/1.6/ChargingProfile.ts | 4 +- src/types/ocpp/1.6/Requests.ts | 8 +++ src/types/ocpp/1.6/Responses.ts | 9 +++ 7 files changed, 115 insertions(+), 9 deletions(-) diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index 0c5addcc..1059a1d0 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -329,11 +329,12 @@ export class ChargingStationUtils { connectorId: number ): number | undefined { let limit: number, matchingChargingProfile: ChargingProfile; - let chargingProfiles: ChargingProfile[] = []; // Get charging profiles for connector and sort by stack level - chargingProfiles = chargingStation - .getConnectorStatus(connectorId) - ?.chargingProfiles?.sort((a, b) => b.stackLevel - a.stackLevel); + const chargingProfiles = Utils.cloneObject( + chargingStation + .getConnectorStatus(connectorId) + ?.chargingProfiles?.sort((a, b) => b.stackLevel - a.stackLevel) ?? [] + ); // Get profiles on connector 0 if (chargingStation.getConnectorStatus(0)?.chargingProfiles) { chargingProfiles.push( @@ -459,10 +460,16 @@ export class ChargingStationUtils { matchingChargingProfile: ChargingProfile; } | null { const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`; + const currentMoment = moment(); + const currentDate = new Date(); for (const chargingProfile of chargingProfiles) { // Set helpers - const currentMoment = moment(); const chargingSchedule = chargingProfile.chargingSchedule; + if (!chargingSchedule?.startSchedule) { + logger.warn( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}` + ); + } // Check type (recurring) and if it is already active // Adjust the daily recurring schedule to today if ( @@ -470,8 +477,12 @@ export class ChargingStationUtils { chargingProfile.recurrencyKind === RecurrencyKindType.DAILY && currentMoment.isAfter(chargingSchedule.startSchedule) ) { - const currentDate = new Date(); - chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule); + if (!(chargingSchedule?.startSchedule instanceof Date)) { + logger.warn( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object` + ); + chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule); + } chargingSchedule.startSchedule.setFullYear( currentDate.getFullYear(), currentDate.getMonth(), diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index cad78436..999e1cea 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -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( + '../../../assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json', + moduleName, + 'constructor' + ), + ], [ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, OCPP16ServiceUtils.parseJsonSchemaFile( @@ -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 diff --git a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts index 38c22013..68e9f7c6 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts @@ -24,6 +24,7 @@ import { type OCPP16DataTransferResponse, type OCPP16DiagnosticsStatusNotificationResponse, type OCPP16FirmwareStatusNotificationResponse, + type OCPP16GetCompositeScheduleResponse, type OCPP16HeartbeatResponse, OCPP16IncomingRequestCommand, type OCPP16MeterValuesRequest, @@ -206,6 +207,14 @@ export class OCPP16ResponseService extends OCPPResponseService { 'constructor' ), ], + [ + OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE, + OCPP16ServiceUtils.parseJsonSchemaFile( + '../../../assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json', + moduleName, + 'constructor' + ), + ], [ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, OCPP16ServiceUtils.parseJsonSchemaFile( diff --git a/src/types/index.ts b/src/types/index.ts index a7c7d24e..6255a3c2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -102,6 +102,7 @@ export { OCPP16ChargePointStatus, type OCPP16ChargingProfile, OCPP16ChargingProfilePurposeType, + type OCPP16ChargingSchedule, type OCPP16ClearCacheRequest, type OCPP16DataTransferRequest, type OCPP16DataTransferResponse, @@ -113,6 +114,8 @@ export { OCPP16FirmwareStatus, type OCPP16FirmwareStatusNotificationRequest, type OCPP16FirmwareStatusNotificationResponse, + type OCPP16GetCompositeScheduleRequest, + type OCPP16GetCompositeScheduleResponse, type OCPP16HeartbeatRequest, type OCPP16HeartbeatResponse, OCPP16IncomingRequestCommand, diff --git a/src/types/ocpp/1.6/ChargingProfile.ts b/src/types/ocpp/1.6/ChargingProfile.ts index 0bfdcd2f..67e12f97 100644 --- a/src/types/ocpp/1.6/ChargingProfile.ts +++ b/src/types/ocpp/1.6/ChargingProfile.ts @@ -9,10 +9,10 @@ export interface OCPP16ChargingProfile extends JsonObject { recurrencyKind?: OCPP16RecurrencyKindType; validFrom?: Date; validTo?: Date; - chargingSchedule: ChargingSchedule; + chargingSchedule: OCPP16ChargingSchedule; } -interface ChargingSchedule extends JsonObject { +export interface OCPP16ChargingSchedule extends JsonObject { duration?: number; startSchedule?: Date; chargingRateUnit: OCPP16ChargingRateUnitType; diff --git a/src/types/ocpp/1.6/Requests.ts b/src/types/ocpp/1.6/Requests.ts index bc451898..b482d140 100644 --- a/src/types/ocpp/1.6/Requests.ts +++ b/src/types/ocpp/1.6/Requests.ts @@ -5,6 +5,7 @@ import type { OCPP16ChargePointStatus, OCPP16ChargingProfile, OCPP16ChargingProfilePurposeType, + OCPP16ChargingRateUnitType, OCPP16DiagnosticsStatus, OCPP16StandardParametersKey, OCPP16VendorParametersKey, @@ -30,6 +31,7 @@ export enum OCPP16IncomingRequestCommand { UNLOCK_CONNECTOR = 'UnlockConnector', GET_CONFIGURATION = 'GetConfiguration', CHANGE_CONFIGURATION = 'ChangeConfiguration', + GET_COMPOSITE_SCHEDULE = 'GetCompositeSchedule', SET_CHARGING_PROFILE = 'SetChargingProfile', CLEAR_CHARGING_PROFILE = 'ClearChargingProfile', REMOTE_START_TRANSACTION = 'RemoteStartTransaction', @@ -100,6 +102,12 @@ export interface ResetRequest extends JsonObject { type: ResetType; } +export interface OCPP16GetCompositeScheduleRequest extends JsonObject { + connectorId: number; + duration: number; + chargingRateUnit?: OCPP16ChargingRateUnitType; +} + export interface SetChargingProfileRequest extends JsonObject { connectorId: number; csChargingProfiles: OCPP16ChargingProfile; diff --git a/src/types/ocpp/1.6/Responses.ts b/src/types/ocpp/1.6/Responses.ts index 1724ce18..e7814c4f 100644 --- a/src/types/ocpp/1.6/Responses.ts +++ b/src/types/ocpp/1.6/Responses.ts @@ -1,6 +1,8 @@ import type { EmptyObject, + GenericStatus, JsonObject, + OCPP16ChargingSchedule, OCPPConfigurationKey, RegistrationStatusEnumType, } from '../../internal'; @@ -49,6 +51,13 @@ export enum OCPP16ChargingProfileStatus { NOT_SUPPORTED = 'NotSupported', } +export interface OCPP16GetCompositeScheduleResponse extends JsonObject { + status: GenericStatus; + connectorId?: number; + scheduleStart?: Date; + chargingSchedule?: OCPP16ChargingSchedule; +} + export interface SetChargingProfileResponse extends JsonObject { status: OCPP16ChargingProfileStatus; } -- 2.34.1