X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2F1.6%2FOCPP16IncomingRequestService.ts;h=dd40ffafd0d89c107fdd3bf3718d4a2615262896;hb=f406808ff62ed65ae71e268884f8b8e78c6fdbc6;hp=e3375d192d1615648f4c0910fcc19aa5a7c84f60;hpb=ad490d5f65e55103448a5c933cf9ea1ae4f512a5;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index e3375d19..dd40ffaf 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -6,14 +6,7 @@ import { URL, fileURLToPath } from 'node:url'; import type { JSONSchemaType } from 'ajv'; import { Client, type FTPResponse } from 'basic-ftp'; -import { - addSeconds, - isDate, - isWithinInterval, - maxTime, - min, - secondsToMilliseconds, -} from 'date-fns'; +import { addSeconds, differenceInSeconds, isDate, maxTime, secondsToMilliseconds } from 'date-fns'; import { create } from 'tar'; import { OCPP16Constants } from './OCPP16Constants'; @@ -21,10 +14,10 @@ import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; import { type ChargingStation, canProceedChargingProfile, - canProceedRecurringChargingProfile, checkChargingStation, getConfigurationKey, - prepareRecurringChargingProfile, + getConnectorChargingProfiles, + prepareChargingProfileKind, removeExpiredReservations, setConfigurationKeyValue, } from '../../../charging-station'; @@ -32,8 +25,6 @@ import { OCPPError } from '../../../exception'; import { type ChangeConfigurationRequest, type ChangeConfigurationResponse, - ChargingProfileKindType, - ChargingRateUnitType, type ClearChargingProfileRequest, type ClearChargingProfileResponse, ErrorType, @@ -101,7 +92,6 @@ import { } from '../../../types'; import { Constants, - cloneObject, convertToDate, convertToInt, formatDurationMilliSeconds, @@ -111,7 +101,6 @@ import { isNotEmptyString, isNullOrUndefined, isUndefined, - isValidTime, logger, sleep, } from '../../../utils'; @@ -475,14 +464,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if (chargingStation.hasConnector(connectorId) === false) { logger.error( `${chargingStation.logPrefix()} Trying to unlock a non existing - connector id ${connectorId.toString()}`, + connector id ${connectorId}`, ); return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED; } if (connectorId === 0) { - logger.error( - `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`, - ); + logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`); return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED; } if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) { @@ -637,8 +624,19 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { } if ( csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE && - (connectorId === 0 || - chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false) + connectorId === 0 + ) { + logger.error( + `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) + on connector ${connectorId}`, + ); + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED; + } + const connectorStatus = chargingStation.getConnectorStatus(connectorId); + if ( + csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE && + connectorId > 0 && + connectorStatus?.transactionStarted === false ) { logger.error( `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) @@ -646,6 +644,20 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ); return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED; } + if ( + csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE && + connectorId > 0 && + connectorStatus?.transactionStarted === true && + csChargingProfiles.transactionId !== connectorStatus?.transactionId + ) { + logger.error( + `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) + on connector ${connectorId} with a different transaction id ${ + csChargingProfiles.transactionId + } than the started transaction id ${connectorStatus?.transactionId}`, + ); + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED; + } OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles); logger.debug( `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`, @@ -682,12 +694,11 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { return OCPP16Constants.OCPP_RESPONSE_REJECTED; } if (chargingRateUnit) { - logger.error( - `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported`, + logger.warn( + `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`, ); - return OCPP16Constants.OCPP_RESPONSE_REJECTED; } - const connectorStatus = chargingStation.getConnectorStatus(connectorId); + const connectorStatus = chargingStation.getConnectorStatus(connectorId)!; if ( isEmptyArray( connectorStatus?.chargingProfiles && @@ -697,19 +708,21 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { return OCPP16Constants.OCPP_RESPONSE_REJECTED; } const currentDate = new Date(); - const interval: Interval = { + const compositeScheduleInterval: Interval = { start: currentDate, end: addSeconds(currentDate, duration), }; - const chargingProfiles: OCPP16ChargingProfile[] = []; - for (const chargingProfile of cloneObject( - (connectorStatus?.chargingProfiles ?? []).concat( - chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [], - ), - ).sort((a, b) => b.stackLevel - a.stackLevel)) { + // Get charging profiles sorted by connector id then stack level + const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles( + chargingStation, + connectorId, + ); + let previousCompositeSchedule: OCPP16ChargingSchedule | undefined; + let compositeSchedule: OCPP16ChargingSchedule | undefined; + for (const chargingProfile of chargingProfiles) { if ( - connectorStatus?.transactionStarted && - isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) + isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) && + connectorStatus?.transactionStarted ) { logger.debug( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${ @@ -719,91 +732,69 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart; } - if (!isDate(chargingProfile.chargingSchedule?.startSchedule)) { + if ( + !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) && + !isDate(chargingProfile.chargingSchedule?.startSchedule) + ) { logger.warn( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${ chargingProfile.chargingProfileId - } startSchedule property is not a Date object. Trying to convert it to a Date object`, + } startSchedule property is not a Date instance. Trying to convert it to a Date instance`, ); chargingProfile.chargingSchedule.startSchedule = convertToDate( chargingProfile.chargingSchedule?.startSchedule, )!; } - switch (chargingProfile.chargingProfileKind) { - case ChargingProfileKindType.RECURRING: - if (!canProceedRecurringChargingProfile(chargingProfile, chargingStation.logPrefix())) { - continue; - } - prepareRecurringChargingProfile( - chargingProfile, - interval.start as Date, - chargingStation.logPrefix(), - ); - break; - case ChargingProfileKindType.RELATIVE: - connectorStatus?.transactionStarted && - (chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart); - break; + if ( + !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) && + isNullOrUndefined(chargingProfile.chargingSchedule?.duration) + ) { + logger.debug( + `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${ + chargingProfile.chargingProfileId + } has no duration defined and will be set to the maximum time allowed`, + ); + // OCPP specifies that if duration is not defined, it should be infinite + chargingProfile.chargingSchedule.duration = differenceInSeconds( + maxTime, + chargingProfile.chargingSchedule.startSchedule!, + ); } if ( - !canProceedChargingProfile( + !prepareChargingProfileKind( + connectorStatus, chargingProfile, - interval.start as Date, + compositeScheduleInterval.start as Date, chargingStation.logPrefix(), ) ) { continue; } - // Add active charging profiles into chargingProfiles array if ( - isValidTime(chargingProfile.chargingSchedule?.startSchedule) && - isWithinInterval(chargingProfile.chargingSchedule.startSchedule!, interval) + !canProceedChargingProfile( + chargingProfile, + compositeScheduleInterval.start as Date, + chargingStation.logPrefix(), + ) ) { - chargingProfiles.push(chargingProfile); + continue; } + compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules( + previousCompositeSchedule, + chargingProfile.chargingSchedule, + compositeScheduleInterval, + ); + previousCompositeSchedule = compositeSchedule; } - const compositeSchedule: OCPP16ChargingSchedule = { - startSchedule: min( - chargingProfiles.map( - (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime, - ), - ), - duration: Math.max( - ...chargingProfiles.map( - (chargingProfile) => chargingProfile.chargingSchedule.duration ?? -Infinity, - ), - ), - chargingRateUnit: chargingProfiles.every( - (chargingProfile) => - chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.AMPERE, - ) - ? ChargingRateUnitType.AMPERE - : chargingProfiles.every( - (chargingProfile) => - chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT, - ) - ? ChargingRateUnitType.WATT - : ChargingRateUnitType.AMPERE, - // FIXME: remove overlapping charging schedule periods - chargingSchedulePeriod: chargingProfiles - .map((chargingProfile) => chargingProfile.chargingSchedule.chargingSchedulePeriod) - .reduce( - (accumulator, value) => - accumulator.concat(value).sort((a, b) => a.startPeriod - b.startPeriod), - [], - ), - minChargeRate: Math.min( - ...chargingProfiles.map( - (chargingProfile) => chargingProfile.chargingSchedule.minChargeRate ?? Infinity, - ), - ), - }; - return { - status: GenericStatus.Accepted, - scheduleStart: compositeSchedule.startSchedule!, - connectorId, - chargingSchedule: compositeSchedule, - }; + if (compositeSchedule) { + return { + status: GenericStatus.Accepted, + scheduleStart: compositeSchedule.startSchedule!, + connectorId, + chargingSchedule: compositeSchedule, + }; + } + return OCPP16Constants.OCPP_RESPONSE_REJECTED; } private handleRequestClearChargingProfile( @@ -827,11 +818,9 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ); return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN; } - if ( - !isNullOrUndefined(connectorId) && - isNotEmptyArray(chargingStation.getConnectorStatus(connectorId!)?.chargingProfiles) - ) { - chargingStation.getConnectorStatus(connectorId!)!.chargingProfiles = []; + const connectorStatus = chargingStation.getConnectorStatus(connectorId!); + if (!isNullOrUndefined(connectorId) && isNotEmptyArray(connectorStatus?.chargingProfiles)) { + connectorStatus!.chargingProfiles = []; logger.debug( `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`, ); @@ -841,11 +830,11 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { let clearedCP = false; if (chargingStation.hasEvses) { for (const evseStatus of chargingStation.evses.values()) { - for (const connectorStatus of evseStatus.connectors.values()) { + for (const status of evseStatus.connectors.values()) { clearedCP = OCPP16ServiceUtils.clearChargingProfiles( chargingStation, commandPayload, - connectorStatus.chargingProfiles, + status.chargingProfiles, ); } } @@ -873,7 +862,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if (chargingStation.hasConnector(connectorId) === false) { logger.error( `${chargingStation.logPrefix()} Trying to change the availability of a - non existing connector id ${connectorId.toString()}`, + non existing connector id ${connectorId}`, ); return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED; } @@ -947,7 +936,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { const remoteStartTransactionLogMsg = ` ${chargingStation.logPrefix()} Transaction remotely STARTED on ${ chargingStation.stationInfo.chargingStationId - }#${transactionConnectorId.toString()} for idTag '${idTag}'`; + }#${transactionConnectorId} for idTag '${idTag}'`; await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, transactionConnectorId, @@ -1035,9 +1024,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { connectorId: number, idTag: string, ): Promise { - if ( - chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available - ) { + const connectorStatus = chargingStation.getConnectorStatus(connectorId); + if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) { await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, connectorId, @@ -1046,9 +1034,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { } logger.warn( `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id - ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus( - connectorId, - )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`, + ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`, ); return OCPP16Constants.OCPP_RESPONSE_REJECTED; } @@ -1058,10 +1044,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { connectorId: number, chargingProfile: OCPP16ChargingProfile, ): boolean { - if ( - chargingProfile && - chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE - ) { + if (chargingProfile?.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) { OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile); logger.debug( `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction @@ -1069,18 +1052,13 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { chargingProfile, ); return true; - } else if ( - chargingProfile && - chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE - ) { - logger.warn( - `${chargingStation.logPrefix()} Not allowed to set ${ - chargingProfile.chargingProfilePurpose - } charging profile(s) at remote start transaction`, - ); - return false; } - return true; + logger.warn( + `${chargingStation.logPrefix()} Not allowed to set ${ + chargingProfile.chargingProfilePurpose + } charging profile(s) at remote start transaction`, + ); + return false; } private async handleRequestRemoteStopTransaction( @@ -1110,7 +1088,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { } logger.warn( `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id - ${transactionId.toString()}`, + ${transactionId}`, ); return OCPP16Constants.OCPP_RESPONSE_REJECTED; } @@ -1394,16 +1372,16 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { } throw new OCPPError( ErrorType.GENERIC_ERROR, - `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${ - uploadResponse?.code && `|${uploadResponse?.code.toString()}` + `Diagnostics transfer failed with error code ${accessResponse.code}${ + uploadResponse?.code && `|${uploadResponse?.code}` }`, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, ); } throw new OCPPError( ErrorType.GENERIC_ERROR, - `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${ - uploadResponse?.code && `|${uploadResponse?.code.toString()}` + `Diagnostics transfer failed with error code ${accessResponse.code}${ + uploadResponse?.code && `|${uploadResponse?.code}` }`, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, );