From 8ad81d3454c0c4599123ec954a982a7804c45eb8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 9 Nov 2025 19:22:46 +0100 Subject: [PATCH] fix: properly resolve evse id MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/ChargingStation.ts | 12 ++++ .../ocpp/2.0/OCPP20ServiceUtils.ts | 12 +++- src/charging-station/ocpp/OCPPServiceUtils.ts | 2 +- tests/ChargingStationFactory.test.ts | 55 +++++++++++++++++++ tests/ChargingStationFactory.ts | 11 ++++ 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index cf1a9f81..125f10de 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -487,6 +487,18 @@ export class ChargingStation extends EventEmitter { ) } + public getEvseIdByConnectorId (connectorId: number): number | undefined { + if (!this.hasEvses) { + return undefined + } + for (const [evseId, evseStatus] of this.evses) { + if (evseStatus.connectors.has(connectorId)) { + return evseId + } + } + return undefined + } + public getHeartbeatInterval (): number { const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval) if (HeartbeatInterval != null) { diff --git a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts index 11c1ab1c..5e48dd0c 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts @@ -109,7 +109,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils { ) } - public static async requestStopTransaction( + public static async requestStopTransaction ( chargingStation: ChargingStation, connectorId: number ): Promise { @@ -133,10 +133,18 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils { return OCPP20Constants.OCPP_RESPONSE_REJECTED } + const evseId = chargingStation.getEvseIdByConnectorId(connectorId) + if (evseId == null) { + logger.error( + `${chargingStation.logPrefix()} OCPP20ServiceUtils.requestStopTransaction: Cannot find EVSE ID for connector ${connectorId.toString()}` + ) + return OCPP20Constants.OCPP_RESPONSE_REJECTED + } + const transactionEventRequest: OCPP20TransactionEventRequest = { eventType: OCPP20TransactionEventEnumType.Ended, evse: { - id: connectorId, + id: evseId, }, seqNo: 0, // This should be managed by the transaction sequence timestamp: new Date(), diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index df70b9b1..c82e9a7e 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -114,7 +114,7 @@ const buildStatusNotificationRequest = ( connectorId, connectorStatus: status as OCPP20ConnectorStatusEnumType, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - evseId: evseId!, + evseId: evseId ?? chargingStation.getEvseIdByConnectorId(connectorId)!, timestamp: new Date(), } satisfies OCPP20StatusNotificationRequest default: diff --git a/tests/ChargingStationFactory.test.ts b/tests/ChargingStationFactory.test.ts index f01cef39..a99753ae 100644 --- a/tests/ChargingStationFactory.test.ts +++ b/tests/ChargingStationFactory.test.ts @@ -469,6 +469,61 @@ await describe('ChargingStationFactory', async () => { }) }) + await describe('getEvseIdByConnectorId', async () => { + await it('Should return undefined for stations without EVSEs', () => { + const station = createChargingStation({ + connectorsCount: 3, + stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, // OCPP 1.6 doesn't use EVSEs + }) + + expect(station.getEvseIdByConnectorId(1)).toBeUndefined() + expect(station.getEvseIdByConnectorId(2)).toBeUndefined() + }) + + await it('Should return correct EVSE ID for connectors in EVSE mode', () => { + const station = createChargingStation({ + connectorsCount: 6, + evseConfiguration: { evsesCount: 2 }, // 2 EVSEs with 3 connectors each + stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, + }) + + // EVSE 1 should have connectors 1, 2, 3 + expect(station.getEvseIdByConnectorId(1)).toBe(1) + expect(station.getEvseIdByConnectorId(2)).toBe(1) + expect(station.getEvseIdByConnectorId(3)).toBe(1) + + // EVSE 2 should have connectors 4, 5, 6 + expect(station.getEvseIdByConnectorId(4)).toBe(2) + expect(station.getEvseIdByConnectorId(5)).toBe(2) + expect(station.getEvseIdByConnectorId(6)).toBe(2) + }) + + await it('Should return undefined for non-existent connector IDs', () => { + const station = createChargingStation({ + connectorsCount: 4, + evseConfiguration: { evsesCount: 2 }, + stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, + }) + + expect(station.getEvseIdByConnectorId(0)).toBeUndefined() // Connector 0 not in EVSEs + expect(station.getEvseIdByConnectorId(99)).toBeUndefined() // Non-existent connector + expect(station.getEvseIdByConnectorId(-1)).toBeUndefined() // Invalid connector ID + }) + + await it('Should handle single EVSE with multiple connectors', () => { + const station = createChargingStation({ + connectorsCount: 3, + evseConfiguration: { evsesCount: 1 }, // Single EVSE with all connectors + stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, + }) + + // All connectors should belong to EVSE 1 + expect(station.getEvseIdByConnectorId(1)).toBe(1) + expect(station.getEvseIdByConnectorId(2)).toBe(1) + expect(station.getEvseIdByConnectorId(3)).toBe(1) + }) + }) + await describe('isConnectorAvailable', async () => { await it('Should return false for connector ID 0', () => { const station = createChargingStation({ connectorsCount: 2 }) diff --git a/tests/ChargingStationFactory.ts b/tests/ChargingStationFactory.ts index 5bda606a..4a7a2b8e 100644 --- a/tests/ChargingStationFactory.ts +++ b/tests/ChargingStationFactory.ts @@ -111,6 +111,17 @@ export function createChargingStation (options: ChargingStationOptions = {}): Ch } return chargingStation.connectors.get(connectorId) }, + getEvseIdByConnectorId: (connectorId: number) => { + if (!chargingStation.hasEvses) { + return undefined + } + for (const [evseId, evseStatus] of chargingStation.evses.entries()) { + if (evseStatus.connectors.has(connectorId)) { + return evseId + } + } + return undefined + }, getEvseIdByTransactionId: (transactionId: string) => { // Search through EVSEs to find one with matching transaction ID if (chargingStation.hasEvses) { -- 2.43.0