BootReasonEnumType,
type ChargingProfile,
ChargingProfileKindType,
+ ChargingProfilePurposeType,
ChargingRateUnitType,
type ChargingSchedulePeriod,
type ChargingStationConfiguration,
}
}
-export const buildConnectorsMap = (
- connectors: Record<string, ConnectorStatus>,
- logPrefix: string,
- templateFile: string
-): Map<number, ConnectorStatus> => {
- const connectorsMap = new Map<number, ConnectorStatus>()
- if (getMaxNumberOfConnectors(connectors) > 0) {
- for (const connector in connectors) {
- const connectorStatus = connectors[connector]
- const connectorId = convertToInt(connector)
- checkStationInfoConnectorStatus(connectorId, connectorStatus, logPrefix, templateFile)
- connectorsMap.set(connectorId, clone<ConnectorStatus>(connectorStatus))
- }
- } else {
- logger.warn(
- `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
- )
- }
- return connectorsMap
-}
-
export const setChargingStationOptions = (
stationInfo: ChargingStationInfo,
options?: ChargingStationOptions
return stationInfo
}
+export const buildConnectorsMap = (
+ connectors: Record<string, ConnectorStatus>,
+ logPrefix: string,
+ templateFile: string
+): Map<number, ConnectorStatus> => {
+ const connectorsMap = new Map<number, ConnectorStatus>()
+ if (getMaxNumberOfConnectors(connectors) > 0) {
+ for (const connector in connectors) {
+ const connectorStatus = connectors[connector]
+ const connectorId = convertToInt(connector)
+ checkStationInfoConnectorStatus(connectorId, connectorStatus, logPrefix, templateFile)
+ connectorsMap.set(connectorId, clone<ConnectorStatus>(connectorStatus))
+ }
+ } else {
+ logger.warn(
+ `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
+ )
+ }
+ return connectorsMap
+}
+
export const initializeConnectorsMapStatus = (
connectors: Map<number, ConnectorStatus>,
logPrefix: string
if (connectorStatus == null) {
return
}
- connectorStatus.chargingProfiles =
- connectorStatus.transactionId != null && isNotEmptyArray(connectorStatus.chargingProfiles)
- ? connectorStatus.chargingProfiles.filter(
- chargingProfile => chargingProfile.transactionId !== connectorStatus.transactionId
- )
- : []
+ if (isNotEmptyArray(connectorStatus.chargingProfiles)) {
+ connectorStatus.chargingProfiles = connectorStatus.chargingProfiles.filter(
+ chargingProfile =>
+ (chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE &&
+ chargingProfile.transactionId != null &&
+ connectorStatus.transactionId != null &&
+ chargingProfile.transactionId !== connectorStatus.transactionId) ||
+ chargingProfile.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE
+ )
+ }
resetAuthorizeConnectorStatus(connectorStatus)
connectorStatus.transactionRemoteStarted = false
connectorStatus.transactionStarted = false
delete connectorStatus.transactionBeginMeterValue
}
+export const prepareConnectorStatus = (connectorStatus: ConnectorStatus): ConnectorStatus => {
+ if (connectorStatus.reservation != null) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ connectorStatus.reservation.expiryDate = convertToDate(connectorStatus.reservation.expiryDate)!
+ }
+ if (isNotEmptyArray(connectorStatus.chargingProfiles)) {
+ connectorStatus.chargingProfiles = connectorStatus.chargingProfiles
+ .filter(
+ chargingProfile =>
+ chargingProfile.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE
+ )
+ .map(chargingProfile => {
+ chargingProfile.chargingSchedule.startSchedule = convertToDate(
+ chargingProfile.chargingSchedule.startSchedule
+ )
+ chargingProfile.validFrom = convertToDate(chargingProfile.validFrom)
+ chargingProfile.validTo = convertToDate(chargingProfile.validTo)
+ return chargingProfile
+ })
+ }
+ return connectorStatus
+}
+
export const createBootNotificationRequest = (
stationInfo: ChargingStationInfo,
bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
return unitDivider
}
+const getChargingStationChargingProfiles = (
+ chargingStation: ChargingStation
+): ChargingProfile[] => {
+ return (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [])
+ .filter(
+ chargingProfile =>
+ chargingProfile.chargingProfilePurpose ===
+ ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE
+ )
+ .sort((a, b) => b.stackLevel - a.stackLevel)
+}
+
+export const getChargingStationChargingProfilesLimit = (
+ chargingStation: ChargingStation
+): number | undefined => {
+ const chargingProfiles = getChargingStationChargingProfiles(chargingStation)
+ if (isNotEmptyArray(chargingProfiles)) {
+ const chargingProfilesLimit = getChargingProfilesLimit(chargingStation, 0, chargingProfiles)
+ if (chargingProfilesLimit != null) {
+ const limit = buildChargingProfilesLimit(chargingStation, chargingProfilesLimit)
+ const chargingStationMaximumPower =
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ chargingStation.stationInfo!.maximumPower!
+ if (limit > chargingStationMaximumPower) {
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingStationChargingProfilesLimit: Charging profile id ${
+ chargingProfilesLimit.chargingProfile.chargingProfileId
+ } limit ${limit} is greater than charging station maximum ${chargingStationMaximumPower}: %j`,
+ chargingProfilesLimit
+ )
+ return chargingStationMaximumPower
+ }
+ return limit
+ }
+ }
+}
+
/**
- * Gets the connector cloned charging profiles applying a power limitation
- * and sorted by connector id descending then stack level descending
+ * Gets the connector charging profiles relevant for power limitation shallow cloned
+ * and sorted by priorities
*
- * @param chargingStation -
- * @param connectorId -
+ * @param chargingStation - Charging station
+ * @param connectorId - Connector id
* @returns connector charging profiles array
*/
export const getConnectorChargingProfiles = (
chargingStation: ChargingStation,
connectorId: number
): ChargingProfile[] => {
- return clone<ChargingProfile[]>(
- (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? [])
- .sort((a, b) => b.stackLevel - a.stackLevel)
- .concat(
- (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? []).sort(
- (a, b) => b.stackLevel - a.stackLevel
+ return (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? [])
+ .slice()
+ .sort((a, b) => {
+ if (
+ a.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE &&
+ b.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE
+ ) {
+ return -1
+ } else if (
+ a.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE &&
+ b.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE
+ ) {
+ return 1
+ }
+ return b.stackLevel - a.stackLevel
+ })
+ .concat(
+ (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [])
+ .filter(
+ chargingProfile =>
+ chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE
)
- )
- )
+ .sort((a, b) => b.stackLevel - a.stackLevel)
+ )
}
-export const getChargingStationConnectorChargingProfilesPowerLimit = (
+export const getConnectorChargingProfilesLimit = (
chargingStation: ChargingStation,
connectorId: number
): number | undefined => {
- let limit: number | undefined, chargingProfile: ChargingProfile | undefined
- // Get charging profiles sorted by connector id then stack level
const chargingProfiles = getConnectorChargingProfiles(chargingStation, connectorId)
if (isNotEmptyArray(chargingProfiles)) {
- const result = getLimitFromChargingProfiles(
+ const chargingProfilesLimit = getChargingProfilesLimit(
chargingStation,
connectorId,
- chargingProfiles,
- chargingStation.logPrefix()
+ chargingProfiles
)
- if (result != null) {
- limit = result.limit
- chargingProfile = result.chargingProfile
- switch (chargingStation.stationInfo?.currentOutType) {
- case CurrentType.AC:
- limit =
- chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
- ? limit
- : ACElectricUtils.powerTotal(
- chargingStation.getNumberOfPhases(),
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- chargingStation.stationInfo.voltageOut!,
- limit
- )
- break
- case CurrentType.DC:
- limit =
- chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
- ? limit
- : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit)
- }
+ if (chargingProfilesLimit != null) {
+ const limit = buildChargingProfilesLimit(chargingStation, chargingProfilesLimit)
const connectorMaximumPower =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
chargingStation.stationInfo!.maximumPower! / chargingStation.powerDivider!
if (limit > connectorMaximumPower) {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${
- chargingProfile.chargingProfileId
- } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
- result
+ `${chargingStation.logPrefix()} ${moduleName}.getConnectorChargingProfilesLimit: Charging profile id ${
+ chargingProfilesLimit.chargingProfile.chargingProfileId
+ } limit ${limit} is greater than connector ${connectorId} maximum ${connectorMaximumPower}: %j`,
+ chargingProfilesLimit
)
- limit = connectorMaximumPower
+ return connectorMaximumPower
}
+ return limit
}
}
- return limit
+}
+
+const buildChargingProfilesLimit = (
+ chargingStation: ChargingStation,
+ chargingProfilesLimit: ChargingProfilesLimit
+): number => {
+ const errorMsg = `Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in charging station information, cannot build charging profiles limit`
+ const { limit, chargingProfile } = chargingProfilesLimit
+ switch (chargingStation.stationInfo?.currentOutType) {
+ case CurrentType.AC:
+ return chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+ ? limit
+ : ACElectricUtils.powerTotal(
+ chargingStation.getNumberOfPhases(),
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ chargingStation.stationInfo.voltageOut!,
+ limit
+ )
+ case CurrentType.DC:
+ return chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+ ? limit
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit)
+ default:
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.buildChargingProfilesLimit: ${errorMsg}`
+ )
+ throw new BaseError(errorMsg)
+ }
}
export const getDefaultVoltageOut = (
}
/**
- * Charging profiles shall already be sorted by connector id descending then stack level descending
+ * Get the charging profiles limit for a connector
+ * Charging profiles shall already be sorted by priorities
*
* @param chargingStation -
* @param connectorId -
* @param chargingProfiles -
- * @param logPrefix -
* @returns ChargingProfilesLimit
*/
-const getLimitFromChargingProfiles = (
+const getChargingProfilesLimit = (
chargingStation: ChargingStation,
connectorId: number,
- chargingProfiles: ChargingProfile[],
- logPrefix: string
+ chargingProfiles: ChargingProfile[]
): ChargingProfilesLimit | undefined => {
- const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`
+ const debugLogMsg = `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profiles limit found: %j`
const currentDate = new Date()
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ let previousActiveChargingProfile: ChargingProfile | undefined
for (const chargingProfile of chargingProfiles) {
const chargingSchedule = chargingProfile.chargingSchedule
if (chargingSchedule.startSchedule == null) {
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`
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: 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)) {
logger.warn(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance`
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance`
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
chargingSchedule.startSchedule = convertToDate(chargingSchedule.startSchedule)!
}
if (chargingSchedule.duration == null) {
logger.debug(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined and will be set to the maximum time allowed`
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: 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)) {
+ if (
+ !prepareChargingProfileKind(
+ connectorStatus,
+ chargingProfile,
+ currentDate,
+ chargingStation.logPrefix()
+ )
+ ) {
continue
}
- if (!canProceedChargingProfile(chargingProfile, currentDate, logPrefix)) {
+ if (!canProceedChargingProfile(chargingProfile, currentDate, chargingStation.logPrefix())) {
continue
}
// Check if the charging profile is active
)
) {
logger.warn(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period`
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period`
)
chargingSchedule.chargingSchedulePeriod.sort(chargingSchedulePeriodCompareFn)
}
// 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`
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod} is not equal to 0`
)
continue
}
// Handle only one schedule period
if (chargingSchedule.chargingSchedulePeriod.length === 1) {
- const result: ChargingProfilesLimit = {
+ const chargingProfilesLimit: ChargingProfilesLimit = {
limit: chargingSchedule.chargingSchedulePeriod[0].limit,
chargingProfile
}
- logger.debug(debugLogMsg, result)
- return result
+ logger.debug(debugLogMsg, chargingProfilesLimit)
+ return chargingProfilesLimit
}
let previousChargingSchedulePeriod: ChargingSchedulePeriod | undefined
// Search for the right schedule period
)
) {
// Found the schedule period: previous is the correct one
- const result: ChargingProfilesLimit = {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- limit: previousChargingSchedulePeriod!.limit,
- chargingProfile
+ const chargingProfilesLimit: ChargingProfilesLimit = {
+ limit: previousChargingSchedulePeriod?.limit ?? chargingSchedulePeriod.limit,
+ chargingProfile: previousActiveChargingProfile ?? chargingProfile
}
- logger.debug(debugLogMsg, result)
- return result
+ logger.debug(debugLogMsg, chargingProfilesLimit)
+ return chargingProfilesLimit
}
- // Keep a reference to previous one
- previousChargingSchedulePeriod = chargingSchedulePeriod
// Handle the last schedule period within the charging profile duration
if (
index === chargingSchedule.chargingSchedulePeriod.length - 1 ||
chargingSchedule.startSchedule
) > chargingSchedule.duration)
) {
- const result: ChargingProfilesLimit = {
- limit: previousChargingSchedulePeriod.limit,
+ const chargingProfilesLimit: ChargingProfilesLimit = {
+ limit: chargingSchedulePeriod.limit,
chargingProfile
}
- logger.debug(debugLogMsg, result)
- return result
+ logger.debug(debugLogMsg, chargingProfilesLimit)
+ return chargingProfilesLimit
}
+ // Keep a reference to previous charging schedule period
+ previousChargingSchedulePeriod = chargingSchedulePeriod
}
}
+ // Keep a reference to previous active charging profile
+ previousActiveChargingProfile = chargingProfile
}
}
}