]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor: add helper to lookup evse id
authorJérôme Benoit <jerome.benoit@sap.com>
Sun, 9 Nov 2025 20:32:22 +0000 (21:32 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sun, 9 Nov 2025 20:32:22 +0000 (21:32 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
tests/ChargingStationFactory.test.ts
tests/ChargingStationFactory.ts

index 125f10de7590685bf15e4b04b8490e6de1d1f46c..9569f9b1d81072887bb1638bde35d1b16706ac79 100644 (file)
@@ -499,6 +499,21 @@ export class ChargingStation extends EventEmitter {
     return undefined
   }
 
+  public getEvseIdByTransactionId (transactionId: number | string | undefined): number | undefined {
+    if (transactionId == null) {
+      return undefined
+    } else if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.transactionId === transactionId) {
+            return evseId
+          }
+        }
+      }
+    }
+    return undefined
+  }
+
   public getHeartbeatInterval (): number {
     const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)
     if (HeartbeatInterval != null) {
index d6330deac51aded0d045d0cf61e00e8796c817d5..bde666dc9b8227c797d0e99391c2c104dd74b3e2 100644 (file)
@@ -1113,6 +1113,16 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       }
     }
 
+    const evseId = chargingStation.getEvseIdByTransactionId(transactionId)
+    if (evseId == null) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStopTransaction: Transaction ID ${transactionId} does not exist on any EVSE`
+      )
+      return {
+        status: RequestStartStopStatusEnumType.Rejected,
+      }
+    }
+
     const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)
     if (connectorId == null) {
       logger.warn(
@@ -1126,7 +1136,8 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     try {
       const stopResponse = await OCPP20ServiceUtils.requestStopTransaction(
         chargingStation,
-        connectorId
+        connectorId,
+        evseId
       )
 
       if (stopResponse.status === GenericStatus.Accepted) {
index 5e48dd0cdf7f404d64111853bc60baedb26cf7ec..b46b45dcfb49dce5609f45a9871415b13ba55d2f 100644 (file)
@@ -111,7 +111,8 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
 
   public static async requestStopTransaction (
     chargingStation: ChargingStation,
-    connectorId: number
+    connectorId: number,
+    evseId?: number
   ): Promise<GenericResponse> {
     const connectorStatus = chargingStation.getConnectorStatus(connectorId)
     if (connectorStatus?.transactionStarted && connectorStatus.transactionId != null) {
@@ -133,7 +134,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
         return OCPP20Constants.OCPP_RESPONSE_REJECTED
       }
 
-      const evseId = chargingStation.getEvseIdByConnectorId(connectorId)
+      evseId = evseId ?? chargingStation.getEvseIdByConnectorId(connectorId)
       if (evseId == null) {
         logger.error(
           `${chargingStation.logPrefix()} OCPP20ServiceUtils.requestStopTransaction: Cannot find EVSE ID for connector ${connectorId.toString()}`
index a99753ae0787f71654f8365b8b35a08207b269d9..a3080dd1d0e2344a34d7775869c98bd218b728ca 100644 (file)
@@ -524,6 +524,197 @@ await describe('ChargingStationFactory', async () => {
       })
     })
 
+    await describe('getEvseIdByTransactionId', async () => {
+      await it('Should return undefined for null transaction ID', () => {
+        const station = createChargingStation({
+          connectorsCount: 2,
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        // Test null handling (matches real class behavior)
+        expect(station.getEvseIdByTransactionId(null)).toBeUndefined()
+      })
+
+      await it('Should return undefined for undefined transaction ID', () => {
+        const station = createChargingStation({
+          connectorsCount: 2,
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        // Test undefined handling (matches real class behavior)
+        expect(station.getEvseIdByTransactionId(undefined)).toBeUndefined()
+      })
+
+      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
+        })
+
+        // Set up transactions on connectors (should still return undefined without EVSEs)
+        const connector1Status = station.getConnectorStatus(1)
+        if (connector1Status) {
+          connector1Status.transactionId = 'test-transaction-123'
+        }
+
+        expect(station.getEvseIdByTransactionId('test-transaction-123')).toBeUndefined()
+      })
+
+      await it('Should return correct EVSE ID when transaction ID matches (single EVSE)', () => {
+        const station = createChargingStation({
+          connectorsCount: 3,
+          evseConfiguration: { evsesCount: 1 }, // Single EVSE with all connectors
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        // Set up transactions on different connectors within the same EVSE
+        const connector1Status = station.getConnectorStatus(1)
+        const connector2Status = station.getConnectorStatus(2)
+        if (connector1Status) {
+          connector1Status.transactionId = 'transaction-on-connector-1'
+        }
+        if (connector2Status) {
+          connector2Status.transactionId = 456
+        }
+
+        // Both should return EVSE ID 1
+        expect(station.getEvseIdByTransactionId('transaction-on-connector-1')).toBe(1)
+        expect(station.getEvseIdByTransactionId(456)).toBe(1)
+      })
+
+      await it('Should return correct EVSE ID when transaction ID matches (multiple EVSEs)', () => {
+        const station = createChargingStation({
+          connectorsCount: 6,
+          evseConfiguration: { evsesCount: 2 }, // 2 EVSEs with 3 connectors each
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        // Set up transactions on connectors in different EVSEs
+        const connector1Status = station.getConnectorStatus(1) // EVSE 1
+        const connector4Status = station.getConnectorStatus(4) // EVSE 2
+        const connector5Status = station.getConnectorStatus(5) // EVSE 2
+
+        if (connector1Status) {
+          connector1Status.transactionId = 'evse1-transaction'
+        }
+        if (connector4Status) {
+          connector4Status.transactionId = 'evse2-transaction-a'
+        }
+        if (connector5Status) {
+          connector5Status.transactionId = 999
+        }
+
+        // Verify correct EVSE mapping
+        expect(station.getEvseIdByTransactionId('evse1-transaction')).toBe(1)
+        expect(station.getEvseIdByTransactionId('evse2-transaction-a')).toBe(2)
+        expect(station.getEvseIdByTransactionId(999)).toBe(2)
+      })
+
+      await it('Should return undefined when transaction ID does not match any connector', () => {
+        const station = createChargingStation({
+          connectorsCount: 4,
+          evseConfiguration: { evsesCount: 2 },
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        // Set up some transactions
+        const connector1Status = station.getConnectorStatus(1)
+        if (connector1Status) {
+          connector1Status.transactionId = 'existing-transaction'
+        }
+
+        expect(station.getEvseIdByTransactionId('non-existent-transaction')).toBeUndefined()
+        expect(station.getEvseIdByTransactionId(12345)).toBeUndefined()
+      })
+
+      await it('Should handle numeric transaction IDs', () => {
+        const station = createChargingStation({
+          connectorsCount: 4,
+          evseConfiguration: { evsesCount: 2 },
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        // Set up numeric transaction ID on connector in EVSE 2
+        const connector3Status = station.getConnectorStatus(3)
+        if (connector3Status) {
+          connector3Status.transactionId = 789
+        }
+
+        expect(station.getEvseIdByTransactionId(789)).toBe(2)
+      })
+
+      await it('Should handle string transaction IDs', () => {
+        const station = createChargingStation({
+          connectorsCount: 4,
+          evseConfiguration: { evsesCount: 2 },
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        // Set up string transaction ID on connector in EVSE 1
+        const connector2Status = station.getConnectorStatus(2)
+        if (connector2Status) {
+          connector2Status.transactionId = 'string-transaction-id-abc123'
+        }
+
+        expect(station.getEvseIdByTransactionId('string-transaction-id-abc123')).toBe(1)
+      })
+
+      await it('Should maintain consistency with getConnectorIdByTransactionId', () => {
+        const station = createChargingStation({
+          connectorsCount: 6,
+          evseConfiguration: { evsesCount: 3 },
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        const testTransactionId = 'consistency-test-transaction'
+
+        // Set up a transaction on connector 5 (which should be in EVSE 3)
+        const connector5Status = station.getConnectorStatus(5)
+        if (connector5Status) {
+          connector5Status.transactionId = testTransactionId
+        }
+
+        // Both methods should find the transaction
+        const foundConnectorId = station.getConnectorIdByTransactionId(testTransactionId)
+        const foundEvseId = station.getEvseIdByTransactionId(testTransactionId)
+
+        expect(foundConnectorId).toBe(5)
+        expect(foundEvseId).toBe(3)
+
+        // Verify consistency: getEvseIdByConnectorId should match
+        if (foundConnectorId !== undefined) {
+          expect(station.getEvseIdByConnectorId(foundConnectorId)).toBe(foundEvseId)
+        }
+      })
+
+      await it('Should handle mixed transaction ID types correctly', () => {
+        const station = createChargingStation({
+          connectorsCount: 4,
+          evseConfiguration: { evsesCount: 2 },
+          stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
+        })
+
+        // Set up mixed types of transaction IDs
+        const connector1Status = station.getConnectorStatus(1)
+        const connector3Status = station.getConnectorStatus(3)
+
+        if (connector1Status) {
+          connector1Status.transactionId = 'string-transaction'
+        }
+        if (connector3Status) {
+          connector3Status.transactionId = 999
+        }
+
+        // Test string transaction ID
+        expect(station.getEvseIdByTransactionId('string-transaction')).toBe(1)
+        // Test numeric transaction ID
+        expect(station.getEvseIdByTransactionId(999)).toBe(2)
+        // Test wrong type should not match
+        expect(station.getEvseIdByTransactionId('999')).toBeUndefined() // String vs number
+        expect(station.getEvseIdByTransactionId(Number('string-transaction'))).toBeUndefined() // NaN
+      })
+    })
+
     await describe('isConnectorAvailable', async () => {
       await it('Should return false for connector ID 0', () => {
         const station = createChargingStation({ connectorsCount: 2 })
index 4a7a2b8ebae53f5c8ccbc0bdec9e8c0159b65694..93490b3eea941b097f1b5953ca27d26cbf216331 100644 (file)
@@ -122,7 +122,10 @@ export function createChargingStation (options: ChargingStationOptions = {}): Ch
       }
       return undefined
     },
-    getEvseIdByTransactionId: (transactionId: string) => {
+    getEvseIdByTransactionId: (transactionId: number | string | undefined) => {
+      if (transactionId == null) {
+        return undefined
+      }
       // Search through EVSEs to find one with matching transaction ID
       if (chargingStation.hasEvses) {
         for (const [evseId, evseStatus] of chargingStation.evses.entries()) {