X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FHelpers.ts;h=068bc6c970afeaa9e402cc4f69086ccafae59ded;hb=a51a4ead39e6926f7f63c6eb6871c59fe9b555b7;hp=f263daa473be009ae0f5c4b980ed0b137012e2da;hpb=88499f52f95a8030eb2186b918fac160b95e58a5;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/Helpers.ts b/src/charging-station/Helpers.ts index f263daa4..068bc6c9 100644 --- a/src/charging-station/Helpers.ts +++ b/src/charging-station/Helpers.ts @@ -16,6 +16,7 @@ import { isDate, isPast, isWithinInterval, + maxTime, toDate, } from 'date-fns'; @@ -261,7 +262,7 @@ export const checkConnectorsConfiguration = ( checkConfiguredMaxConnectors(configuredMaxConnectors, logPrefix, templateFile); const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors!); checkTemplateMaxConnectors(templateMaxConnectors, logPrefix, templateFile); - const templateMaxAvailableConnectors = stationTemplate.Connectors![0] + const templateMaxAvailableConnectors = stationTemplate.Connectors?.[0] ? templateMaxConnectors - 1 : templateMaxConnectors; if ( @@ -338,6 +339,12 @@ export const initializeConnectorsMapStatus = ( }; export const resetConnectorStatus = (connectorStatus: ConnectorStatus): void => { + connectorStatus.chargingProfiles = + connectorStatus.transactionId && isNotEmptyArray(connectorStatus.chargingProfiles) + ? connectorStatus.chargingProfiles?.filter( + (chargingProfile) => chargingProfile.transactionId !== connectorStatus.transactionId, + ) + : []; connectorStatus.idTagLocalAuthorized = false; connectorStatus.idTagAuthorized = false; connectorStatus.transactionRemoteStarted = false; @@ -445,12 +452,9 @@ export const stationTemplateToStationInfo = ( export const createSerialNumber = ( stationTemplate: ChargingStationTemplate, stationInfo: ChargingStationInfo, - params: { + params?: { randomSerialNumberUpperCase?: boolean; randomSerialNumber?: boolean; - } = { - randomSerialNumberUpperCase: true, - randomSerialNumber: true, }, ): void => { params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params }; @@ -514,17 +518,36 @@ export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInf return unitDivider; }; +/** + * Gets the connector cloned charging profiles applying a power limitation + * and sorted by connector id descending then stack level descending + * + * @param chargingStation - + * @param connectorId - + * @returns connector charging profiles array + */ +export const getConnectorChargingProfiles = ( + chargingStation: ChargingStation, + connectorId: number, +) => { + return cloneObject( + (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? []) + .sort((a, b) => b.stackLevel - a.stackLevel) + .concat( + (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? []).sort( + (a, b) => b.stackLevel - a.stackLevel, + ), + ), + ); +}; + export const getChargingStationConnectorChargingProfilesPowerLimit = ( chargingStation: ChargingStation, connectorId: number, ): number | undefined => { let limit: number | undefined, chargingProfile: ChargingProfile | undefined; - // Get charging profiles for connector id and sort by stack level - const chargingProfiles = cloneObject( - (chargingStation.getConnectorStatus(connectorId)?.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 = getConnectorChargingProfiles(chargingStation, connectorId); if (isNotEmptyArray(chargingProfiles)) { const result = getLimitFromChargingProfiles( chargingStation, @@ -622,7 +645,7 @@ const getConfiguredMaxNumberOfConnectors = (stationTemplate: ChargingStationTemp } else if (isUndefined(stationTemplate.numberOfConnectors) === false) { configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors as number; } else if (stationTemplate.Connectors && !stationTemplate.Evses) { - configuredMaxNumberOfConnectors = stationTemplate.Connectors[0] + configuredMaxNumberOfConnectors = stationTemplate.Connectors?.[0] ? getMaxNumberOfConnectors(stationTemplate.Connectors) - 1 : getMaxNumberOfConnectors(stationTemplate.Connectors); } else if (stationTemplate.Evses && !stationTemplate.Connectors) { @@ -686,7 +709,7 @@ const warnDeprecatedTemplateKey = ( templateFile: string, logMsgToAppend = '', ): void => { - if (!isUndefined(template[key as keyof ChargingStationTemplate])) { + if (!isUndefined(template?.[key as keyof ChargingStationTemplate])) { const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${ isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : '' }`; @@ -700,7 +723,7 @@ const convertDeprecatedTemplateKey = ( deprecatedKey: string, key?: string, ): void => { - if (!isUndefined(template[deprecatedKey as keyof ChargingStationTemplate])) { + if (!isUndefined(template?.[deprecatedKey as keyof ChargingStationTemplate])) { if (!isUndefined(key)) { (template as unknown as Record)[key!] = template[deprecatedKey as keyof ChargingStationTemplate]; @@ -715,7 +738,7 @@ interface ChargingProfilesLimit { } /** - * Charging profiles shall already be sorted by connector id and stack level (highest stack level has priority) + * Charging profiles shall already be sorted by connector id descending then stack level descending * * @param chargingStation - * @param connectorId - @@ -732,27 +755,34 @@ const getLimitFromChargingProfiles = ( const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`; const currentDate = new Date(); const connectorStatus = chargingStation.getConnectorStatus(connectorId)!; - if (!isArraySorted(chargingProfiles, (a, b) => b.stackLevel - a.stackLevel)) { - logger.warn( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profiles are not sorted by stack level. Trying to sort them`, - ); - chargingProfiles.sort((a, b) => b.stackLevel - a.stackLevel); - } for (const chargingProfile of chargingProfiles) { const chargingSchedule = chargingProfile.chargingSchedule; - if (connectorStatus?.transactionStarted && isNullOrUndefined(chargingSchedule?.startSchedule)) { + if (isNullOrUndefined(chargingSchedule?.startSchedule) && connectorStatus?.transactionStarted) { logger.debug( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined. Trying to set it to the connector current transaction start date`, ); // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction chargingSchedule.startSchedule = connectorStatus?.transactionStart; } - if (!isDate(chargingSchedule?.startSchedule)) { + if ( + !isNullOrUndefined(chargingSchedule?.startSchedule) && + !isDate(chargingSchedule?.startSchedule) + ) { logger.warn( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date object. Trying to convert it to a Date object`, + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance`, ); chargingSchedule.startSchedule = convertToDate(chargingSchedule?.startSchedule)!; } + if ( + !isNullOrUndefined(chargingSchedule?.startSchedule) && + isNullOrUndefined(chargingSchedule?.duration) + ) { + logger.debug( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: 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 + chargingSchedule.duration = differenceInSeconds(maxTime, chargingSchedule.startSchedule!); + } if (!prepareChargingProfileKind(connectorStatus, chargingProfile, currentDate, logPrefix)) { continue; } @@ -761,7 +791,6 @@ const getLimitFromChargingProfiles = ( } // Check if the charging profile is active if ( - isValidTime(chargingSchedule?.startSchedule) && isWithinInterval(currentDate, { start: chargingSchedule.startSchedule!, end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), @@ -773,17 +802,17 @@ const getLimitFromChargingProfiles = ( b: ChargingSchedulePeriod, ) => a.startPeriod - b.startPeriod; if ( - isArraySorted( + !isArraySorted( chargingSchedule.chargingSchedulePeriod, chargingSchedulePeriodCompareFn, - ) === false + ) ) { logger.warn( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period`, ); chargingSchedule.chargingSchedulePeriod.sort(chargingSchedulePeriodCompareFn); } - // Check if the first schedule period start period is equal to 0 + // Check if the first schedule period startPeriod property is equal to 0 if (chargingSchedule.chargingSchedulePeriod[0].startPeriod !== 0) { logger.error( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod} is not equal to 0`, @@ -815,7 +844,7 @@ const getLimitFromChargingProfiles = ( // Found the schedule period: previous is the correct one const result: ChargingProfilesLimit = { limit: previousChargingSchedulePeriod!.limit, - chargingProfile: chargingProfile, + chargingProfile, }; logger.debug(debugLogMsg, result); return result; @@ -836,7 +865,7 @@ const getLimitFromChargingProfiles = ( ) { const result: ChargingProfilesLimit = { limit: previousChargingSchedulePeriod.limit, - chargingProfile: chargingProfile, + chargingProfile, }; logger.debug(debugLogMsg, result); return result; @@ -863,12 +892,14 @@ export const prepareChargingProfileKind = ( case ChargingProfileKindType.RELATIVE: if (!isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule)) { logger.warn( - `${logPrefix} ${moduleName}.prepareChargingProfileKind: Charging profile id ${chargingProfile.chargingProfileId} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`, + `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfile.chargingProfileId} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`, ); delete chargingProfile.chargingSchedule.startSchedule; } - connectorStatus?.transactionStarted && - (chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart); + if (connectorStatus?.transactionStarted) { + chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart; + } + // FIXME: Handle relative charging profile duration break; } return true; @@ -890,16 +921,30 @@ export const canProceedChargingProfile = ( ); return false; } - const chargingSchedule = chargingProfile.chargingSchedule; - if (isNullOrUndefined(chargingSchedule?.startSchedule)) { + if ( + isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule) || + isNullOrUndefined(chargingProfile.chargingSchedule.duration) + ) { + logger.error( + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule or duration defined`, + ); + return false; + } + if ( + !isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule) && + !isValidTime(chargingProfile.chargingSchedule.startSchedule) + ) { logger.error( - `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined`, + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has an invalid startSchedule date defined`, ); return false; } - if (isNullOrUndefined(chargingSchedule?.duration)) { + if ( + !isNullOrUndefined(chargingProfile.chargingSchedule.duration) && + !Number.isSafeInteger(chargingProfile.chargingSchedule.duration) + ) { logger.error( - `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`, + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has non integer duration defined`, ); return false; } @@ -919,6 +964,15 @@ const canProceedRecurringChargingProfile = ( ); return false; } + if ( + chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING && + isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule) + ) { + logger.error( + `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined`, + ); + return false; + } return true; }; @@ -982,7 +1036,7 @@ const prepareRecurringChargingProfile = ( break; default: logger.error( - `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} recurrency kind ${chargingProfile.recurrencyKind} is not supported`, + `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${chargingProfile.recurrencyKind} charging profile id ${chargingProfile.chargingProfileId} is not supported`, ); } if (recurringIntervalTranslated && !isWithinInterval(currentDate, recurringInterval!)) {