return undefined
}
+ /**
+ * Returns the EVSE status for the given EVSE ID.
+ * @param evseId - The EVSE ID to look up
+ * @returns The EvseStatus if found, undefined otherwise
+ */
+ public getEvseStatus (evseId: number): EvseStatus | undefined {
+ return this.evses.get(evseId)
+ }
+
public getHeartbeatInterval (): number {
const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)
if (HeartbeatInterval != null) {
return isPast(reservation.expiryDate)
}
+/**
+ * Checks if a connector has a pending (non-expired) reservation.
+ * @param connectorStatus - The connector status to check
+ * @returns true if the connector has a pending reservation, false otherwise
+ */
+export const hasPendingReservation = (connectorStatus: ConnectorStatus): boolean => {
+ return connectorStatus.reservation != null && !hasReservationExpired(connectorStatus.reservation)
+}
+
+/**
+ * Checks if a charging station has any pending (non-expired) reservations.
+ * @param chargingStation - The charging station to check
+ * @returns true if any connector has a pending reservation, false otherwise
+ */
+export const hasPendingReservations = (chargingStation: ChargingStation): boolean => {
+ if (chargingStation.hasEvses) {
+ for (const evseStatus of chargingStation.evses.values()) {
+ for (const connectorStatus of evseStatus.connectors.values()) {
+ if (hasPendingReservation(connectorStatus)) {
+ return true
+ }
+ }
+ }
+ } else {
+ for (const connectorStatus of chargingStation.connectors.values()) {
+ if (hasPendingReservation(connectorStatus)) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
export const removeExpiredReservations = async (
chargingStation: ChargingStation
): Promise<void> => {
getConnectorChargingProfiles,
getIdTagsFile,
hasFeatureProfile,
+ hasPendingReservation,
+ hasPendingReservations,
hasReservationExpired,
prepareChargingProfileKind,
removeExpiredReservations,
validateUUID,
} from '../../../utils/index.js'
import { getConfigurationKey } from '../../ConfigurationKeyUtils.js'
-import { getIdTagsFile, hasReservationExpired, resetConnectorStatus } from '../../Helpers.js'
+import {
+ getIdTagsFile,
+ hasPendingReservation,
+ hasPendingReservations,
+ resetConnectorStatus,
+} from '../../Helpers.js'
import { OCPPAuthServiceFactory } from '../auth/services/OCPPAuthServiceFactory.js'
import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
import { restoreConnectorStatus, sendAndSetConnectorStatus } from '../OCPPServiceUtils.js'
// Check for EVSE-specific active transactions if evseId is provided
let evseHasActiveTransactions = false
if (evseId !== undefined && evseId > 0) {
- const evse = chargingStation.evses.get(evseId)
+ const evse = chargingStation.getEvseStatus(evseId)
if (evse != null) {
evseHasActiveTransactions = this.hasEvseActiveTransactions(evse)
}
// OnIdle reset
if (evseId !== undefined) {
// EVSE-specific OnIdle reset
- const evse = chargingStation.evses.get(evseId)
+ const evse = chargingStation.getEvseStatus(evseId)
if (evse != null && !this.isEvseIdle(chargingStation, evse)) {
logger.info(
`${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle EVSE reset scheduled for EVSE ${evseId.toString()}, waiting for idle state`
}
// Get the first connector for this EVSE
- const evse = chargingStation.evses.get(evseId)
+ const evse = chargingStation.getEvseStatus(evseId)
if (evse == null) {
const errorMsg = `EVSE ${evseId.toString()} does not exist on charging station`
logger.warn(
*/
private hasEvseActiveTransactions (evse: EvseStatus): boolean {
for (const connector of evse.connectors.values()) {
- if (connector.transactionId !== undefined) {
+ if (connector.transactionId != null) {
return true
}
}
*/
private hasEvsePendingReservations (evse: EvseStatus): boolean {
for (const connector of evse.connectors.values()) {
- if (connector.reservation != null && !hasReservationExpired(connector.reservation)) {
+ if (hasPendingReservation(connector)) {
return true
}
}
)
}
- /**
- * Checks if charging station has any non-expired reservations per OCPP 2.0.1 Errata idle definition.
- * @param chargingStation - The charging station instance
- * @returns true if any connector has a pending (non-expired) reservation
- */
- private hasPendingReservations (chargingStation: ChargingStation): boolean {
- if (chargingStation.hasEvses) {
- for (const evse of chargingStation.evses.values()) {
- for (const connector of evse.connectors.values()) {
- if (connector.reservation != null && !hasReservationExpired(connector.reservation)) {
- return true
- }
- }
- }
- } else {
- for (const connector of chargingStation.connectors.values()) {
- if (connector.reservation != null && !hasReservationExpired(connector.reservation)) {
- return true
- }
- }
- }
- return false
- }
-
/**
* Checks if charging station is idle per OCPP 2.0.1 Errata definition.
* Idle means: no active transactions, no firmware update in progress, no pending reservations.
return (
chargingStation.getNumberOfRunningTransactions() === 0 &&
!this.hasFirmwareUpdateInProgress(chargingStation) &&
- !this.hasPendingReservations(chargingStation)
+ !hasPendingReservations(chargingStation)
)
}
// Reset EVSE - this would typically involve resetting the EVSE hardware/software
// For now, we'll restore connectors to available status after a short delay
setTimeout(() => {
- const evse = chargingStation.evses.get(evseId)
+ const evse = chargingStation.getEvseStatus(evseId)
if (evse) {
for (const [connectorId] of evse.connectors) {
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
*/
private scheduleEvseResetOnIdle (chargingStation: ChargingStation, evseId: number): void {
const monitorInterval = setInterval(() => {
- const evse = chargingStation.evses.get(evseId)
+ const evse = chargingStation.getEvseStatus(evseId)
if (evse != null) {
if (this.isEvseIdle(chargingStation, evse)) {
clearInterval(monitorInterval)
evseId: number,
status: OCPP20ConnectorStatusEnumType
): void {
- const evse = chargingStation.evses.get(evseId)
+ const evse = chargingStation.getEvseStatus(evseId)
if (evse) {
for (const [connectorId] of evse.connectors) {
sendAndSetConnectorStatus(
for (const [evseId, evse] of chargingStation.evses) {
for (const [connectorId, connector] of evse.connectors) {
- if (connector.transactionId !== undefined) {
+ if (connector.transactionId != null) {
logger.info(
`${chargingStation.logPrefix()} ${moduleName}.terminateAllTransactions: Terminating transaction ${connector.transactionId.toString()} on connector ${connectorId.toString()}`
)
evseId: number,
reason: OCPP20ReasonEnumType
): Promise<void> {
- const evse = chargingStation.evses.get(evseId)
+ const evse = chargingStation.getEvseStatus(evseId)
if (!evse) {
logger.warn(
`${chargingStation.logPrefix()} ${moduleName}.terminateEvseTransactions: EVSE ${evseId.toString()} not found`
const terminationPromises: Promise<unknown>[] = []
for (const [connectorId, connector] of evse.connectors) {
- if (connector.transactionId !== undefined) {
+ if (connector.transactionId != null) {
logger.info(
`${chargingStation.logPrefix()} ${moduleName}.terminateEvseTransactions: Terminating transaction ${connector.transactionId.toString()} on connector ${connectorId.toString()}`
)
getHashId,
getMaxNumberOfEvses,
getPhaseRotationValue,
+ hasPendingReservation,
+ hasPendingReservations,
+ hasReservationExpired,
validateStationInfo,
} from '../../src/charging-station/Helpers.js'
import { BaseError } from '../../src/exception/index.js'
type ConnectorStatus,
ConnectorStatusEnum,
OCPPVersion,
+ type Reservation,
} from '../../src/types/index.js'
import { logger } from '../../src/utils/Logger.js'
import { createChargingStation, createChargingStationTemplate } from '../ChargingStationFactory.js'
const baseName = 'CS-TEST'
const chargingStationTemplate = createChargingStationTemplate(baseName)
+ // Helper to create test reservations with configurable expiry
+ const createTestReservation = (expired = false): Reservation =>
+ ({
+ connectorId: 1,
+ expiryDate: new Date(Date.now() + (expired ? -60000 : 60000)),
+ idTag: 'tag1',
+ reservationId: 1,
+ }) as Reservation
+
await it('Verify getChargingStationId()', () => {
expect(getChargingStationId(1, chargingStationTemplate)).toBe(`${baseName}-00001`)
})
ConnectorStatusEnum.Available
)
})
+
+ // Tests for reservation helper functions
+ await it('Verify hasReservationExpired() - expired reservation', () => {
+ expect(hasReservationExpired(createTestReservation(true))).toBe(true)
+ })
+
+ await it('Verify hasReservationExpired() - valid reservation', () => {
+ expect(hasReservationExpired(createTestReservation(false))).toBe(false)
+ })
+
+ await it('Verify hasPendingReservation() - no reservation', () => {
+ const connectorStatus = {} as ConnectorStatus
+ expect(hasPendingReservation(connectorStatus)).toBe(false)
+ })
+
+ await it('Verify hasPendingReservation() - with valid reservation', () => {
+ const connectorStatus = { reservation: createTestReservation(false) } as ConnectorStatus
+ expect(hasPendingReservation(connectorStatus)).toBe(true)
+ })
+
+ await it('Verify hasPendingReservation() - with expired reservation', () => {
+ const connectorStatus = { reservation: createTestReservation(true) } as ConnectorStatus
+ expect(hasPendingReservation(connectorStatus)).toBe(false)
+ })
+
+ await it('Verify hasPendingReservations() - no reservations (without EVSEs)', () => {
+ const chargingStation = createChargingStation({ baseName, connectorsCount: 2 })
+ expect(hasPendingReservations(chargingStation)).toBe(false)
+ })
+
+ await it('Verify hasPendingReservations() - with pending reservation (without EVSEs)', () => {
+ const chargingStation = createChargingStation({ baseName, connectorsCount: 2 })
+ const connectorStatus = chargingStation.connectors.get(1)
+ if (connectorStatus != null) {
+ connectorStatus.reservation = createTestReservation(false)
+ }
+ expect(hasPendingReservations(chargingStation)).toBe(true)
+ })
+
+ await it('Verify hasPendingReservations() - no reservations (with EVSEs)', () => {
+ const chargingStation = createChargingStation({
+ baseName,
+ connectorsCount: 2,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+ })
+ expect(hasPendingReservations(chargingStation)).toBe(false)
+ })
+
+ await it('Verify hasPendingReservations() - with pending reservation (with EVSEs)', () => {
+ const chargingStation = createChargingStation({
+ baseName,
+ connectorsCount: 2,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+ })
+ const firstEvse = chargingStation.evses.get(1)
+ const firstConnector = firstEvse?.connectors.values().next().value
+ if (firstConnector != null) {
+ firstConnector.reservation = createTestReservation(false)
+ }
+ expect(hasPendingReservations(chargingStation)).toBe(true)
+ })
+
+ await it('Verify hasPendingReservations() - with expired reservation only (with EVSEs)', () => {
+ const chargingStation = createChargingStation({
+ baseName,
+ connectorsCount: 2,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+ })
+ const firstEvse = chargingStation.evses.get(1)
+ const firstConnector = firstEvse?.connectors.values().next().value
+ if (firstConnector != null) {
+ firstConnector.reservation = createTestReservation(true)
+ }
+ expect(hasPendingReservations(chargingStation)).toBe(false)
+ })
})