]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor: encapsulate connector/EVSE iteration behind generator API
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 28 Mar 2026 20:15:44 +0000 (21:15 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 28 Mar 2026 20:20:13 +0000 (21:20 +0100)
Introduce iterateConnectors() and iterateEvses() generators on
ChargingStation, replacing ~30 duplicated if(hasEvses)/else iteration
patterns across the codebase. Make connectors and evses Maps private
to enforce usage of the new API.

- Add ConnectorEntry and EvseEntry as unified types for both iteration
  and serialization, removing duplicate ConnectorDataEntry/EvseDataEntry
- Add hasEvse() accessor method for EVSE existence checks
- Fix remoteStartId not propagated in TransactionEvent (F01.FR.25)
- Fix messagesInQueue in GetTransactionStatus to check actual queue
- Fix OCPP 1.6 changeAvailability losing SCHEDULED response
- Consolidate ad-hoc test mock factories to use createMockChargingStation
- Update UI wire format and components for unified types

BREAKING CHANGE: connectors and evses Maps are now private on
ChargingStation. Use iterateConnectors(), iterateEvses(), hasEvse(),
getEvseStatus(), getConnectorStatus() instead of direct Map access.

35 files changed:
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/Helpers.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/types/ChargingStationWorker.ts
src/types/ConnectorStatus.ts
src/types/Evse.ts
src/types/index.ts
src/utils/ChargingStationConfigurationUtils.ts
tests/charging-station/ChargingStation-Lifecycle.test.ts
tests/charging-station/ChargingStation-Resilience.test.ts
tests/charging-station/ChargingStation.test.ts
tests/charging-station/Helpers.test.ts
tests/charging-station/helpers/StationHelpers.ts
tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-SmartCharging.test.ts
tests/charging-station/ocpp/1.6/OCPP16Integration-Transactions.test.ts
tests/charging-station/ocpp/1.6/OCPP16ResponseService-Transactions.test.ts
tests/charging-station/ocpp/1.6/OCPP16TestUtils.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ChangeAvailability.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RemoteStartAuth.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-Reset.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-UnlockConnector.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-UpdateFirmware.test.ts
tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts
tests/utils/ChargingStationConfigurationUtils.test.ts
tests/utils/MessageChannelUtils.test.ts
ui/web/src/components/charging-stations/CSData.vue
ui/web/src/types/ChargingStationType.ts
ui/web/tests/unit/CSData.test.ts
ui/web/tests/unit/constants.ts

index 4b81f25ec433b30257adc32173a1bac2250d710b..654e758392b3676f0ec32de769a37b33d65df0cf 100644 (file)
@@ -255,20 +255,8 @@ export class AutomaticTransactionGenerator {
   }
 
   private initializeConnectorsStatus (): void {
-    if (this.chargingStation.hasEvses) {
-      for (const [evseId, evseStatus] of this.chargingStation.evses) {
-        if (evseId > 0) {
-          for (const connectorId of evseStatus.connectors.keys()) {
-            this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.chargingStation.connectors.keys()) {
-        if (connectorId > 0) {
-          this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
-        }
-      }
+    for (const { connectorId } of this.chargingStation.iterateConnectors(true)) {
+      this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
     }
   }
 
@@ -400,20 +388,8 @@ export class AutomaticTransactionGenerator {
       this.connectorsStatus.clear()
       this.initializeConnectorsStatus()
     }
-    if (this.chargingStation.hasEvses) {
-      for (const [evseId, evseStatus] of this.chargingStation.evses) {
-        if (evseId > 0) {
-          for (const connectorId of evseStatus.connectors.keys()) {
-            this.startConnector(connectorId, stopAbsoluteDuration)
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.chargingStation.connectors.keys()) {
-        if (connectorId > 0) {
-          this.startConnector(connectorId, stopAbsoluteDuration)
-        }
-      }
+    for (const { connectorId } of this.chargingStation.iterateConnectors(true)) {
+      this.startConnector(connectorId, stopAbsoluteDuration)
     }
   }
 
@@ -466,20 +442,8 @@ export class AutomaticTransactionGenerator {
   }
 
   private stopConnectors (): void {
-    if (this.chargingStation.hasEvses) {
-      for (const [evseId, evseStatus] of this.chargingStation.evses) {
-        if (evseId > 0) {
-          for (const connectorId of evseStatus.connectors.keys()) {
-            this.stopConnector(connectorId)
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.chargingStation.connectors.keys()) {
-        if (connectorId > 0) {
-          this.stopConnector(connectorId)
-        }
-      }
+    for (const { connectorId } of this.chargingStation.iterateConnectors(true)) {
+      this.stopConnector(connectorId)
     }
   }
 
index b1223cdd3688ab5f71692fba18f97cf751bc77a9..c5da8219438920fb659f0a6ca351c708a2c43177 100644 (file)
@@ -23,12 +23,14 @@ import {
   type ChargingStationOcppConfiguration,
   type ChargingStationOptions,
   type ChargingStationTemplate,
+  type ConnectorEntry,
   type ConnectorStatus,
   ConnectorStatusEnum,
   CurrentType,
   type ErrorCallback,
   type ErrorResponse,
   ErrorType,
+  type EvseEntry,
   type EvseStatus,
   type EvseStatusConfiguration,
   FileType,
@@ -163,8 +165,6 @@ export class ChargingStation extends EventEmitter {
   public automaticTransactionGenerator?: AutomaticTransactionGenerator
   public bootNotificationRequest?: BootNotificationRequest
   public bootNotificationResponse?: BootNotificationResponse
-  public readonly connectors: Map<number, ConnectorStatus>
-  public readonly evses: Map<number, EvseStatus>
   public heartbeatSetInterval?: NodeJS.Timeout
   public idTagsCache: IdTagsCache
   public readonly index: number
@@ -205,7 +205,9 @@ export class ChargingStation extends EventEmitter {
   private configurationFile!: string
   private configurationFileHash!: string
   private configuredSupervisionUrl!: URL
+  private readonly connectors: Map<number, ConnectorStatus>
   private connectorsConfigurationHash!: string
+  private readonly evses: Map<number, EvseStatus>
   private evsesConfigurationHash!: string
   private flushingMessageBuffer: boolean
   private flushMessageBufferSetInterval?: NodeJS.Timeout
@@ -464,21 +466,10 @@ export class ChargingStation extends EventEmitter {
   ): number | undefined {
     if (transactionId == null) {
       return undefined
-    } else if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-          if (connectorStatus.transactionId === transactionId) {
-            return connectorId
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
-          return connectorId
-        }
-      }
     }
+    return this.iterateConnectors().find(
+      ({ connectorStatus }) => connectorStatus.transactionId === transactionId
+    )?.connectorId
   }
 
   /**
@@ -596,16 +587,10 @@ export class ChargingStation extends EventEmitter {
   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
+    return this.iterateConnectors().find(
+      ({ connectorStatus }) => connectorStatus.transactionId === transactionId
+    )?.evseId
   }
 
   /**
@@ -678,26 +663,11 @@ export class ChargingStation extends EventEmitter {
    * @returns The number of running transactions
    */
   public getNumberOfRunningTransactions (): number {
-    let numberOfRunningTransactions = 0
-    if (this.hasEvses) {
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId === 0) {
-          continue
-        }
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.transactionStarted === true) {
-            ++numberOfRunningTransactions
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-          ++numberOfRunningTransactions
-        }
-      }
-    }
-    return numberOfRunningTransactions
+    return this.iterateConnectors(true).reduce(
+      (count, { connectorStatus }) =>
+        connectorStatus.transactionStarted === true ? count + 1 : count,
+      0
+    )
   }
 
   /**
@@ -710,21 +680,9 @@ export class ChargingStation extends EventEmitter {
     filterKey: ReservationKey,
     value: number | string
   ): Reservation | undefined {
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.reservation?.[filterKey] === value) {
-            return connectorStatus.reservation
-          }
-        }
-      }
-    } else {
-      for (const connectorStatus of this.connectors.values()) {
-        if (connectorStatus.reservation?.[filterKey] === value) {
-          return connectorStatus.reservation
-        }
-      }
-    }
+    return this.iterateConnectors().find(
+      ({ connectorStatus }) => connectorStatus.reservation?.[filterKey] === value
+    )?.connectorStatus.reservation
   }
 
   public getReserveConnectorZeroSupported (): boolean {
@@ -739,21 +697,9 @@ export class ChargingStation extends EventEmitter {
    * @returns The ID tag or undefined if not found
    */
   public getTransactionIdTag (transactionId: number): string | undefined {
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.transactionId === transactionId) {
-            return connectorStatus.transactionIdTag
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
-          return this.getConnectorStatus(connectorId)?.transactionIdTag
-        }
-      }
-    }
+    return this.iterateConnectors().find(
+      ({ connectorStatus }) => connectorStatus.transactionId === transactionId
+    )?.connectorStatus.transactionIdTag
   }
 
   public getVoltageOut (stationInfo?: ChargingStationInfo): Voltage {
@@ -781,6 +727,10 @@ export class ChargingStation extends EventEmitter {
     return this.connectors.has(connectorId)
   }
 
+  public hasEvse (evseId: number): boolean {
+    return this.evses.has(evseId)
+  }
+
   public hasIdTags (): boolean {
     const idTagsFile = this.stationInfo != null ? getIdTagsFile(this.stationInfo) : undefined
     return idTagsFile != null && isNotEmptyArray(this.idTagsCache.getIdTags(idTagsFile))
@@ -846,6 +796,30 @@ export class ChargingStation extends EventEmitter {
     return this.wsConnection?.readyState === WebSocket.OPEN
   }
 
+  public * iterateConnectors (skipZero = false): Generator<ConnectorEntry> {
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (skipZero && evseId === 0) continue
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (skipZero && connectorId === 0) continue
+          yield { connectorId, connectorStatus, evseId }
+        }
+      }
+    } else {
+      for (const [connectorId, connectorStatus] of this.connectors) {
+        if (skipZero && connectorId === 0) continue
+        yield { connectorId, connectorStatus, evseId: undefined }
+      }
+    }
+  }
+
+  public * iterateEvses (skipZero = false): Generator<EvseEntry> {
+    for (const [evseId, evseStatus] of this.evses) {
+      if (skipZero && evseId === 0) continue
+      yield { evseId, evseStatus }
+    }
+  }
+
   public lockConnector (connectorId: number): void {
     if (connectorId === 0) {
       logger.warn(`${this.logPrefix()} lockConnector: connector id 0 is not a physical connector`)
@@ -2118,21 +2092,10 @@ export class ChargingStation extends EventEmitter {
     }
     if (getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation) == null) {
       const connectorsPhaseRotation: string[] = []
-      if (this.hasEvses) {
-        for (const evseStatus of this.evses.values()) {
-          for (const connectorId of evseStatus.connectors.keys()) {
-            const phaseRotation = getPhaseRotationValue(connectorId, this.getNumberOfPhases())
-            if (phaseRotation != null) {
-              connectorsPhaseRotation.push(phaseRotation)
-            }
-          }
-        }
-      } else {
-        for (const connectorId of this.connectors.keys()) {
-          const phaseRotation = getPhaseRotationValue(connectorId, this.getNumberOfPhases())
-          if (phaseRotation != null) {
-            connectorsPhaseRotation.push(phaseRotation)
-          }
+      for (const { connectorId } of this.iterateConnectors()) {
+        const phaseRotation = getPhaseRotationValue(connectorId, this.getNumberOfPhases())
+        if (phaseRotation != null) {
+          connectorsPhaseRotation.push(phaseRotation)
         }
       }
       addConfigurationKey(
@@ -2650,34 +2613,12 @@ export class ChargingStation extends EventEmitter {
       this.startHeartbeat()
     }
     // Initialize connectors status
-    if (this.hasEvses) {
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId > 0) {
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            await sendAndSetConnectorStatus(this, {
-              connectorId,
-              evseId,
-              status: getBootConnectorStatus(this, connectorId, connectorStatus),
-            } as unknown as StatusNotificationRequest)
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0) {
-          const connectorStatus = this.getConnectorStatus(connectorId)
-          if (connectorStatus == null) {
-            logger.error(
-              `${this.logPrefix()} No connector ${connectorId.toString()} status found during message sequence start`
-            )
-            continue
-          }
-          await sendAndSetConnectorStatus(this, {
-            connectorId,
-            status: getBootConnectorStatus(this, connectorId, connectorStatus),
-          } as unknown as StatusNotificationRequest)
-        }
-      }
+    for (const { connectorId, connectorStatus, evseId } of this.iterateConnectors(true)) {
+      await sendAndSetConnectorStatus(this, {
+        connectorId,
+        ...(evseId != null && { evseId }),
+        status: getBootConnectorStatus(this, connectorId, connectorStatus),
+      } as unknown as StatusNotificationRequest)
     }
     if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
       await this.ocppRequestService.requestHandler<
@@ -2739,29 +2680,13 @@ export class ChargingStation extends EventEmitter {
     this.internalStopMessageSequence()
     // Stop ongoing transactions
     stopTransactions && (await stopRunningTransactions(this, reason))
-    if (this.hasEvses) {
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId > 0) {
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            await sendAndSetConnectorStatus(this, {
-              connectorId,
-              evseId,
-              status: ConnectorStatusEnum.Unavailable,
-            } as unknown as StatusNotificationRequest)
-            delete connectorStatus.status
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0) {
-          await sendAndSetConnectorStatus(this, {
-            connectorId,
-            status: ConnectorStatusEnum.Unavailable,
-          } as unknown as StatusNotificationRequest)
-          delete this.getConnectorStatus(connectorId)?.status
-        }
-      }
+    for (const { connectorId, connectorStatus, evseId } of this.iterateConnectors(true)) {
+      await sendAndSetConnectorStatus(this, {
+        connectorId,
+        ...(evseId != null && { evseId }),
+        status: ConnectorStatusEnum.Unavailable,
+      } as unknown as StatusNotificationRequest)
+      delete connectorStatus.status
     }
   }
 
index b2bf0cc887754c3d463ab1ef78431b0b1bc70672..0f49868954e56cba112d6557fd28291a3bf188a3 100644 (file)
@@ -121,19 +121,9 @@ export const hasPendingReservation = (connectorStatus: ConnectorStatus): boolean
  * @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
-      }
+  for (const { connectorStatus } of chargingStation.iterateConnectors()) {
+    if (hasPendingReservation(connectorStatus)) {
+      return true
     }
   }
   return false
@@ -143,25 +133,9 @@ export const removeExpiredReservations = async (
   chargingStation: ChargingStation
 ): Promise<void> => {
   const reservations: Reservation[] = []
-  if (chargingStation.hasEvses) {
-    for (const evseStatus of chargingStation.evses.values()) {
-      for (const connectorStatus of evseStatus.connectors.values()) {
-        if (
-          connectorStatus.reservation != null &&
-          hasReservationExpired(connectorStatus.reservation)
-        ) {
-          reservations.push(connectorStatus.reservation)
-        }
-      }
-    }
-  } else {
-    for (const connectorStatus of chargingStation.connectors.values()) {
-      if (
-        connectorStatus.reservation != null &&
-        hasReservationExpired(connectorStatus.reservation)
-      ) {
-        reservations.push(connectorStatus.reservation)
-      }
+  for (const { connectorStatus } of chargingStation.iterateConnectors()) {
+    if (connectorStatus.reservation != null && hasReservationExpired(connectorStatus.reservation)) {
+      reservations.push(connectorStatus.reservation)
     }
   }
   const results = await Promise.allSettled(
@@ -275,7 +249,7 @@ export const validateStationInfo = (chargingStation: ChargingStation): void => {
   switch (chargingStation.stationInfo.ocppVersion) {
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201:
-      if (isEmpty(chargingStation.evses)) {
+      if (chargingStation.getNumberOfEvses() === 0) {
         throw new BaseError(
           `${chargingStationId}: OCPP ${chargingStation.stationInfo.ocppVersion} requires at least one EVSE defined in the charging station template/configuration`
         )
index 9b4844c6a97a768e79f63d5b56176539e595d1b1..33b802c22dc4ed3923255696c71a9e3b41535963 100644 (file)
@@ -499,29 +499,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
                   }
                 )
                 .catch(errorHandler)
-            } else if (chargingStation.hasEvses) {
-              for (const evseStatus of chargingStation.evses.values()) {
-                for (const [id, connectorStatus] of evseStatus.connectors) {
-                  chargingStation.ocppRequestService
-                    .requestHandler<
-                      OCPP16StatusNotificationRequest,
-                      OCPP16StatusNotificationResponse
-                    >(
-                      chargingStation,
-                      OCPP16RequestCommand.STATUS_NOTIFICATION,
-                      {
-                        connectorId: id,
-                        status: connectorStatus.status as OCPP16ChargePointStatus,
-                      } as unknown as OCPP16StatusNotificationRequest,
-                      {
-                        triggerMessage: true,
-                      }
-                    )
-                    .catch(errorHandler)
-                }
-              }
             } else {
-              for (const [id, connectorStatus] of chargingStation.connectors) {
+              for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors()) {
                 chargingStation.ocppRequestService
                   .requestHandler<
                     OCPP16StatusNotificationRequest,
@@ -530,7 +509,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
                     chargingStation,
                     OCPP16RequestCommand.STATUS_NOTIFICATION,
                     {
-                      connectorId: id,
+                      connectorId,
                       status: connectorStatus.status as OCPP16ChargePointStatus,
                     } as unknown as OCPP16StatusNotificationRequest,
                     {
@@ -716,25 +695,16 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         ? OCPP16ChargePointStatus.Available
         : OCPP16ChargePointStatus.Unavailable
     if (connectorId === 0) {
-      let response: OCPP16ChangeAvailabilityResponse | undefined
-      if (chargingStation.hasEvses) {
-        for (const evseStatus of chargingStation.evses.values()) {
-          response = await OCPP16ServiceUtils.changeAvailability(
-            chargingStation,
-            [...evseStatus.connectors.keys()],
-            chargePointStatus,
-            type
-          )
-        }
-      } else {
-        response = await OCPP16ServiceUtils.changeAvailability(
-          chargingStation,
-          [...chargingStation.connectors.keys()],
-          chargePointStatus,
-          type
-        )
-      }
-      return response ?? OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
+      const response = await OCPP16ServiceUtils.changeAvailability(
+        chargingStation,
+        chargingStation
+          .iterateConnectors()
+          .map(({ connectorId }) => connectorId)
+          .toArray(),
+        chargePointStatus,
+        type
+      )
+      return response
     } else if (
       connectorId > 0 &&
       (chargingStation.isChargingStationAvailable() ||
@@ -908,29 +878,14 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       }
     } else {
       let clearedCP = false
-      if (chargingStation.hasEvses) {
-        for (const evseStatus of chargingStation.evses.values()) {
-          for (const status of evseStatus.connectors.values()) {
-            const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
-              chargingStation,
-              commandPayload,
-              status.chargingProfiles as OCPP16ChargingProfile[]
-            )
-            if (clearedConnectorCP && !clearedCP) {
-              clearedCP = true
-            }
-          }
-        }
-      } else {
-        for (const id of chargingStation.connectors.keys()) {
-          const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
-            chargingStation,
-            commandPayload,
-            chargingStation.getConnectorStatus(id)?.chargingProfiles as OCPP16ChargingProfile[]
-          )
-          if (clearedConnectorCP && !clearedCP) {
-            clearedCP = true
-          }
+      for (const { connectorStatus } of chargingStation.iterateConnectors()) {
+        const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
+          chargingStation,
+          commandPayload,
+          connectorStatus.chargingProfiles as OCPP16ChargingProfile[]
+        )
+        if (clearedConnectorCP && !clearedCP) {
+          clearedCP = true
         }
       }
       if (clearedCP) {
@@ -1683,30 +1638,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     if (!checkChargingStationState(chargingStation, chargingStation.logPrefix())) {
       return
     }
-    if (chargingStation.hasEvses) {
-      for (const [evseId, evseStatus] of chargingStation.evses) {
-        if (evseId > 0) {
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            if (connectorStatus.transactionStarted === false) {
-              await OCPP16ServiceUtils.sendAndSetConnectorStatus(chargingStation, {
-                connectorId,
-                status: OCPP16ChargePointStatus.Unavailable,
-              } as OCPP16StatusNotificationRequest)
-            }
-          }
-        }
-      }
-    } else {
-      for (const connectorId of chargingStation.connectors.keys()) {
-        if (
-          connectorId > 0 &&
-          chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
-        ) {
-          await OCPP16ServiceUtils.sendAndSetConnectorStatus(chargingStation, {
-            connectorId,
-            status: OCPP16ChargePointStatus.Unavailable,
-          } as OCPP16StatusNotificationRequest)
-        }
+    for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors(true)) {
+      if (connectorStatus.transactionStarted === false) {
+        await OCPP16ServiceUtils.sendAndSetConnectorStatus(chargingStation, {
+          connectorId,
+          status: OCPP16ChargePointStatus.Unavailable,
+        } as OCPP16StatusNotificationRequest)
       }
     }
     await chargingStation.ocppRequestService.requestHandler<
@@ -1758,31 +1695,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         transactionsStarted = true
         wasTransactionsStarted = true
       } else {
-        if (chargingStation.hasEvses) {
-          for (const [evseId, evseStatus] of chargingStation.evses) {
-            if (evseId > 0) {
-              for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-                if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
-                  await OCPP16ServiceUtils.sendAndSetConnectorStatus(chargingStation, {
-                    connectorId,
-                    status: OCPP16ChargePointStatus.Unavailable,
-                  } as OCPP16StatusNotificationRequest)
-                }
-              }
-            }
-          }
-        } else {
-          for (const connectorId of chargingStation.connectors.keys()) {
-            if (
-              connectorId > 0 &&
-              chargingStation.getConnectorStatus(connectorId)?.status !==
-                OCPP16ChargePointStatus.Unavailable
-            ) {
-              await OCPP16ServiceUtils.sendAndSetConnectorStatus(chargingStation, {
-                connectorId,
-                status: OCPP16ChargePointStatus.Unavailable,
-              } as OCPP16StatusNotificationRequest)
-            }
+        for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors(true)) {
+          if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
+            await OCPP16ServiceUtils.sendAndSetConnectorStatus(chargingStation, {
+              connectorId,
+              status: OCPP16ChargePointStatus.Unavailable,
+            } as OCPP16StatusNotificationRequest)
           }
         }
         transactionsStarted = false
index c6b383c83824cf869137f051e0c6bfb20b031a17..85e1e7e8a785f76083df706a57c4c1db2c61c0e2 100644 (file)
@@ -150,26 +150,10 @@ export class OCPP16ResponseService extends OCPPResponseService {
     requestPayload: OCPP16AuthorizeRequest
   ): void {
     let authorizeConnectorId: number | undefined
-    if (chargingStation.hasEvses) {
-      for (const [evseId, evseStatus] of chargingStation.evses) {
-        if (evseId > 0) {
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
-              authorizeConnectorId = connectorId
-              break
-            }
-          }
-        }
-      }
-    } else {
-      for (const connectorId of chargingStation.connectors.keys()) {
-        if (
-          connectorId > 0 &&
-          chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
-        ) {
-          authorizeConnectorId = connectorId
-          break
-        }
+    for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors(true)) {
+      if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
+        authorizeConnectorId = connectorId
+        break
       }
     }
     if (authorizeConnectorId != null) {
@@ -344,7 +328,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       return
     }
     if (chargingStation.hasEvses) {
-      for (const [evseId, evseStatus] of chargingStation.evses) {
+      for (const { evseId, evseStatus } of chargingStation.iterateEvses()) {
         if (evseStatus.connectors.size > 1) {
           for (const [id, status] of evseStatus.connectors) {
             if (id !== connectorId && status.transactionStarted === true) {
index c5376875c27fac4111fd357ed6a6d386c6a75966..ecf553d5cbd0e2b2a9dfa386d242c33d8bd6dd24 100644 (file)
@@ -10,6 +10,7 @@ import {
   CertificateSigningUseEnumType,
   ChangeAvailabilityStatusEnumType,
   ConnectorEnumType,
+  type ConnectorStatus,
   ConnectorStatusEnum,
   CustomerInformationStatusEnumType,
   DataEnumType,
@@ -416,7 +417,10 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
               OCPP20TriggerReasonEnumType.RemoteStart,
               connectorId,
               response.transactionId,
-              startedMeterValues.length > 0 ? { meterValue: startedMeterValues } : undefined
+              {
+                ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }),
+                remoteStartId: request.remoteStartId,
+              }
             ).catch((error: unknown) => {
               logger.error(
                 `${chargingStation.logPrefix()} ${moduleName}.constructor: TransactionEvent(Started) error:`,
@@ -510,10 +514,8 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
             if (evse?.id != null && evse.id > 0) {
               targetEvseIds.push(evse.id)
             } else {
-              for (const [eId] of chargingStation.evses) {
-                if (eId > 0) {
-                  targetEvseIds.push(eId)
-                }
+              for (const { evseId } of chargingStation.iterateEvses(true)) {
+                targetEvseIds.push(evseId)
               }
             }
             let hasSentTransactionEvent = false
@@ -918,61 +920,44 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         }
 
         if (chargingStation.hasEvses) {
-          for (const [evseId, evse] of chargingStation.evses) {
+          for (const { evseId, evseStatus } of chargingStation.iterateEvses()) {
             reportData.push({
               component: {
                 evse: { id: evseId },
                 name: OCPP20ComponentName.EVSE,
               },
               variable: { name: OCPP20DeviceInfoVariableName.AvailabilityState },
-              variableAttribute: [{ type: AttributeEnumType.Actual, value: evse.availability }],
+              variableAttribute: [
+                { type: AttributeEnumType.Actual, value: evseStatus.availability },
+              ],
               variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: true },
             })
-            if (evse.connectors.size > 0) {
-              for (const [connectorId, connector] of evse.connectors) {
-                reportData.push({
-                  component: {
-                    evse: { connectorId, id: evseId },
-                    name: OCPP20ComponentName.EVSE,
-                  },
-                  variable: { name: OCPP20DeviceInfoVariableName.ConnectorType },
-                  variableAttribute: [
-                    {
-                      type: AttributeEnumType.Actual,
-                      value: connector.type ?? ConnectorEnumType.Unknown,
-                    },
-                  ],
-                  variableCharacteristics: {
-                    dataType: DataEnumType.string,
-                    supportsMonitoring: false,
-                  },
-                })
-              }
-            }
-          }
-        } else {
-          for (const [connectorId, connector] of chargingStation.connectors) {
-            if (connectorId > 0) {
-              reportData.push({
-                component: {
-                  evse: { connectorId, id: 1 },
-                  name: OCPP20ComponentName.Connector,
-                },
-                variable: { name: OCPP20DeviceInfoVariableName.ConnectorType },
-                variableAttribute: [
-                  {
-                    type: AttributeEnumType.Actual,
-                    value: connector.type ?? ConnectorEnumType.Unknown,
-                  },
-                ],
-                variableCharacteristics: {
-                  dataType: DataEnumType.string,
-                  supportsMonitoring: false,
-                },
-              })
-            }
           }
         }
+        for (const {
+          connectorId,
+          connectorStatus,
+          evseId,
+        } of chargingStation.iterateConnectors()) {
+          if (evseId == null && connectorId === 0) continue
+          reportData.push({
+            component: {
+              evse: { connectorId, id: evseId ?? 1 },
+              name: evseId != null ? OCPP20ComponentName.EVSE : OCPP20ComponentName.Connector,
+            },
+            variable: { name: OCPP20DeviceInfoVariableName.ConnectorType },
+            variableAttribute: [
+              {
+                type: AttributeEnumType.Actual,
+                value: connectorStatus.type ?? ConnectorEnumType.Unknown,
+              },
+            ],
+            variableCharacteristics: {
+              dataType: DataEnumType.string,
+              supportsMonitoring: false,
+            },
+          })
+        }
         break
 
       case ReportBaseEnumType.SummaryInventory:
@@ -993,38 +978,38 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         })
 
         if (chargingStation.hasEvses) {
-          for (const [evseId, evse] of chargingStation.evses) {
+          for (const { evseId, evseStatus } of chargingStation.iterateEvses()) {
             reportData.push({
               component: {
                 evse: { id: evseId },
                 name: OCPP20ComponentName.EVSE,
               },
               variable: { name: OCPP20DeviceInfoVariableName.AvailabilityState },
-              variableAttribute: [{ type: AttributeEnumType.Actual, value: evse.availability }],
+              variableAttribute: [
+                { type: AttributeEnumType.Actual, value: evseStatus.availability },
+              ],
               variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: true },
             })
           }
         } else {
-          for (const [connectorId, connector] of chargingStation.connectors) {
-            if (connectorId > 0) {
-              reportData.push({
-                component: {
-                  evse: { connectorId, id: 1 },
-                  name: OCPP20ComponentName.Connector,
-                },
-                variable: { name: OCPP20DeviceInfoVariableName.AvailabilityState },
-                variableAttribute: [
-                  {
-                    type: AttributeEnumType.Actual,
-                    value: connector.status ?? ConnectorStatusEnum.Unavailable,
-                  },
-                ],
-                variableCharacteristics: {
-                  dataType: DataEnumType.string,
-                  supportsMonitoring: true,
+          for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors(true)) {
+            reportData.push({
+              component: {
+                evse: { connectorId, id: 1 },
+                name: OCPP20ComponentName.Connector,
+              },
+              variable: { name: OCPP20DeviceInfoVariableName.AvailabilityState },
+              variableAttribute: [
+                {
+                  type: AttributeEnumType.Actual,
+                  value: connectorStatus.status ?? ConnectorStatusEnum.Unavailable,
                 },
-              })
-            }
+              ],
+              variableCharacteristics: {
+                dataType: DataEnumType.string,
+                supportsMonitoring: true,
+              },
+            })
           }
         }
         break
@@ -1047,6 +1032,20 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     }
   }
 
+  private connectorHasQueuedEvents (
+    connectorStatus: ConnectorStatus,
+    transactionId?: string
+  ): boolean {
+    const queue = connectorStatus.transactionEventQueue
+    if (queue == null || queue.length === 0) {
+      return false
+    }
+    if (transactionId == null) {
+      return true
+    }
+    return queue.some(({ request }) => request.transactionInfo.transactionId === transactionId)
+  }
+
   private getRestoredConnectorStatus (
     chargingStation: ChargingStation,
     connectorId: number
@@ -1079,7 +1078,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     operationalStatus: OCPP20OperationalStatusEnumType,
     newConnectorStatus: OCPP20ConnectorStatusEnumType
   ): OCPP20ChangeAvailabilityResponse {
-    if (!chargingStation.evses.has(evseId)) {
+    if (!chargingStation.hasEvse(evseId)) {
       return {
         status: ChangeAvailabilityStatusEnumType.Rejected,
         statusInfo: {
@@ -1129,10 +1128,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     newConnectorStatus: OCPP20ConnectorStatusEnumType
   ): OCPP20ChangeAvailabilityResponse | undefined {
     let hasActiveTransactions = false
-    for (const [evseId, evseStatus] of chargingStation.evses) {
-      if (evseId === 0) {
-        continue
-      }
+    for (const { evseId, evseStatus } of chargingStation.iterateEvses(true)) {
       if (this.hasEvseActiveTransactions(evseStatus)) {
         hasActiveTransactions = true
         logger.info(
@@ -1146,8 +1142,8 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       }
     }
     if (hasActiveTransactions) {
-      for (const [evseId, evseStatus] of chargingStation.evses) {
-        if (evseId > 0 && !this.hasEvseActiveTransactions(evseStatus)) {
+      for (const { evseId, evseStatus } of chargingStation.iterateEvses(true)) {
+        if (!this.hasEvseActiveTransactions(evseStatus)) {
           this.sendEvseStatusNotifications(chargingStation, evseId, newConnectorStatus)
         }
       }
@@ -1167,7 +1163,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     operationalStatus: OCPP20OperationalStatusEnumType,
     newConnectorStatus: OCPP20ConnectorStatusEnumType
   ): OCPP20ChangeAvailabilityResponse {
-    if (!chargingStation.evses.has(evseId)) {
+    if (!chargingStation.hasEvse(evseId)) {
       logger.warn(
         `${chargingStation.logPrefix()} ${moduleName}.handleRequestChangeAvailability: EVSE ${evseId.toString()} not found, rejecting`
       )
@@ -1421,10 +1417,8 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     }
 
     // Apply availability change to all EVSEs (for Operative, or Inoperative with no active transactions)
-    for (const [evseId, evseStatus] of chargingStation.evses) {
-      if (evseId > 0) {
-        evseStatus.availability = operationalStatus
-      }
+    for (const { evseStatus } of chargingStation.iterateEvses(true)) {
+      evseStatus.availability = operationalStatus
     }
     if (operationalStatus === OCPP20OperationalStatusEnumType.Operative) {
       this.sendRestoredAllConnectorsStatusNotifications(chargingStation)
@@ -1775,16 +1769,14 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     // E14.FR.06: When transactionId is omitted, ongoingIndicator SHALL NOT be set
     if (transactionId == null) {
       return {
-        // Simulator has no persistent offline message buffer
-        messagesInQueue: false,
+        messagesInQueue: this.hasQueuedTransactionEvents(chargingStation),
       }
     }
 
     const evseId = chargingStation.getEvseIdByTransactionId(transactionId)
 
     return {
-      // Simulator has no persistent offline message buffer
-      messagesInQueue: false,
+      messagesInQueue: this.hasQueuedTransactionEvents(chargingStation, transactionId),
       ongoingIndicator: evseId != null,
     }
   }
@@ -1946,7 +1938,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         }
       }
 
-      const evseExists = chargingStation.evses.has(evseId)
+      const evseExists = chargingStation.hasEvse(evseId)
       if (!evseExists) {
         logger.warn(
           `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: EVSE ${evseId.toString()} not found, rejecting reset request`
@@ -2676,7 +2668,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         }
       }
 
-      if (!chargingStation.evses.has(evseId)) {
+      if (!chargingStation.hasEvse(evseId)) {
         return {
           status: UnlockStatusEnumType.UnknownConnector,
           statusInfo: {
@@ -2784,9 +2776,9 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       }
     }
 
-    const hasActiveTransactions = [...chargingStation.evses].some(
-      ([evseId, evse]) => evseId > 0 && this.hasEvseActiveTransactions(evse)
-    )
+    const hasActiveTransactions = chargingStation
+      .iterateEvses(true)
+      .some(({ evseStatus }) => this.hasEvseActiveTransactions(evseStatus))
     if (hasActiveTransactions) {
       logger.info(
         `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Active transactions detected — installation will be deferred until idle`
@@ -2860,6 +2852,18 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     )
   }
 
+  private hasQueuedTransactionEvents (
+    chargingStation: ChargingStation,
+    transactionId?: string
+  ): boolean {
+    for (const { connectorStatus } of chargingStation.iterateConnectors()) {
+      if (this.connectorHasQueuedEvents(connectorStatus, transactionId)) {
+        return true
+      }
+    }
+    return false
+  }
+
   private isAuthorizedToStopTransaction (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -3061,7 +3065,10 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     const evseIds =
       evseId != null && evseId > 0
         ? [evseId]
-        : [...chargingStation.evses.keys()].filter(id => id > 0)
+        : chargingStation
+          .iterateEvses(true)
+          .map(({ evseId }) => evseId)
+          .toArray()
     for (const id of evseIds) {
       const evseStatus = chargingStation.getEvseStatus(id)
       if (evseStatus != null) {
@@ -3167,10 +3174,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
   }
 
   private selectAvailableEvse (chargingStation: ChargingStation): number | undefined {
-    for (const [evseId, evseStatus] of chargingStation.evses) {
-      if (evseId === 0) {
-        continue
-      }
+    for (const { evseId, evseStatus } of chargingStation.iterateEvses(true)) {
       if (
         evseStatus.availability !== OCPP20OperationalStatusEnumType.Inoperative &&
         !this.hasEvseActiveTransactions(evseStatus)
@@ -3185,18 +3189,16 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     chargingStation: ChargingStation,
     status: OCPP20ConnectorStatusEnumType
   ): void {
-    for (const [, evse] of chargingStation.evses) {
-      for (const [connectorId] of evse.connectors) {
-        sendAndSetConnectorStatus(chargingStation, {
-          connectorId,
-          connectorStatus: status,
-        } as unknown as OCPP20StatusNotificationRequest).catch((error: unknown) => {
-          logger.error(
-            `${chargingStation.logPrefix()} ${moduleName}.sendAllConnectorsStatusNotifications: Error sending status notification for connector ${connectorId.toString()}:`,
-            error
-          )
-        })
-      }
+    for (const { connectorId } of chargingStation.iterateConnectors()) {
+      sendAndSetConnectorStatus(chargingStation, {
+        connectorId,
+        connectorStatus: status,
+      } as unknown as OCPP20StatusNotificationRequest).catch((error: unknown) => {
+        logger.error(
+          `${chargingStation.logPrefix()} ${moduleName}.sendAllConnectorsStatusNotifications: Error sending status notification for connector ${connectorId.toString()}:`,
+          error
+        )
+      })
     }
   }
 
@@ -3347,21 +3349,17 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
   }
 
   private sendRestoredAllConnectorsStatusNotifications (chargingStation: ChargingStation): void {
-    for (const [evseId, evseStatus] of chargingStation.evses) {
-      if (evseId > 0) {
-        for (const [connectorId] of evseStatus.connectors) {
-          const restoredStatus = this.getRestoredConnectorStatus(chargingStation, connectorId)
-          sendAndSetConnectorStatus(chargingStation, {
-            connectorId,
-            connectorStatus: restoredStatus,
-          } as unknown as OCPP20StatusNotificationRequest).catch((error: unknown) => {
-            logger.error(
-              `${chargingStation.logPrefix()} ${moduleName}.sendRestoredAllConnectorsStatusNotifications: Error sending status notification for connector ${connectorId.toString()}:`,
-              error
-            )
-          })
-        }
-      }
+    for (const { connectorId } of chargingStation.iterateConnectors(true)) {
+      const restoredStatus = this.getRestoredConnectorStatus(chargingStation, connectorId)
+      sendAndSetConnectorStatus(chargingStation, {
+        connectorId,
+        connectorStatus: restoredStatus,
+      } as unknown as OCPP20StatusNotificationRequest).catch((error: unknown) => {
+        logger.error(
+          `${chargingStation.logPrefix()} ${moduleName}.sendRestoredAllConnectorsStatusNotifications: Error sending status notification for connector ${connectorId.toString()}:`,
+          error
+        )
+      })
     }
   }
 
@@ -3552,9 +3550,9 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
 
     // L01.FR.06: Wait for active transactions to end before installing
     // L01.FR.07: Set idle connectors to Unavailable when AllowNewSessionsPendingFirmwareUpdate is false/absent
-    const hasActiveTransactionsBeforeInstall = [...chargingStation.evses].some(
-      ([evseId, evse]) => evseId > 0 && this.hasEvseActiveTransactions(evse)
-    )
+    const hasActiveTransactionsBeforeInstall = chargingStation
+      .iterateEvses(true)
+      .some(({ evseStatus }) => this.hasEvseActiveTransactions(evseStatus))
     if (hasActiveTransactionsBeforeInstall) {
       const variableManager = OCPP20VariableManager.getInstance()
       const allowNewSessionsResults = variableManager.getVariables(chargingStation, [
@@ -3567,17 +3565,17 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       const allowNewSessions = convertToBoolean(allowNewSessionsResults[0]?.attributeValue)
       while (
         !checkAborted() &&
-        [...chargingStation.evses].some(
-          ([evseId, evse]) => evseId > 0 && this.hasEvseActiveTransactions(evse)
-        )
+        chargingStation
+          .iterateEvses(true)
+          .some(({ evseStatus }) => this.hasEvseActiveTransactions(evseStatus))
       ) {
         // L01.FR.07: Set newly-available EVSE to Unavailable on each iteration
         if (!allowNewSessions) {
-          for (const [fwEvseId, fwEvseStatus] of chargingStation.evses) {
-            if (fwEvseId > 0 && !this.hasEvseActiveTransactions(fwEvseStatus)) {
+          for (const { evseId, evseStatus } of chargingStation.iterateEvses(true)) {
+            if (!this.hasEvseActiveTransactions(evseStatus)) {
               this.sendEvseStatusNotifications(
                 chargingStation,
-                fwEvseId,
+                evseId,
                 OCPP20ConnectorStatusEnumType.Unavailable
               )
             }
@@ -3685,18 +3683,16 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     chargingStation: ChargingStation,
     errorHandler: (error: unknown) => void
   ): void {
-    for (const [evseId, evseStatus] of chargingStation.evses) {
-      if (evseId > 0) {
-        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-          const resolvedStatus = connectorStatus.status ?? ConnectorStatusEnum.Available
-          chargingStation.ocppRequestService
-            .requestHandler<
-              OCPP20StatusNotificationRequest,
-              OCPP20StatusNotificationResponse
-            >(chargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, { connectorId, connectorStatus: resolvedStatus, evseId } as unknown as OCPP20StatusNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
-            .catch(errorHandler)
-        }
-      }
+    for (const { connectorId, connectorStatus, evseId } of chargingStation.iterateConnectors(
+      true
+    )) {
+      const resolvedStatus = connectorStatus.status ?? ConnectorStatusEnum.Available
+      chargingStation.ocppRequestService
+        .requestHandler<
+          OCPP20StatusNotificationRequest,
+          OCPP20StatusNotificationResponse
+        >(chargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, { connectorId, connectorStatus: resolvedStatus, evseId } as unknown as OCPP20StatusNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
+        .catch(errorHandler)
     }
   }
 
@@ -3706,7 +3702,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     errorHandler: (error: unknown) => void
   ): void {
     if (evse?.id !== undefined && evse.id > 0 && evse.connectorId !== undefined) {
-      const evseStatus = chargingStation.evses.get(evse.id)
+      const evseStatus = chargingStation.getEvseStatus(evse.id)
       const connectorStatus = evseStatus?.connectors.get(evse.connectorId)
       const resolvedStatus = connectorStatus?.status ?? ConnectorStatusEnum.Available
       chargingStation.ocppRequestService
@@ -4040,7 +4036,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         },
       }
     }
-    if (!chargingStation.evses.has(evse.id)) {
+    if (!chargingStation.hasEvse(evse.id)) {
       return {
         status: TriggerMessageStatusEnumType.Rejected,
         statusInfo: {
index f56c25ef3b1aa65022e83a71c9710da79981119c..015bfe15a36373e32483ba91c7d1aa951d64c802 100644 (file)
@@ -742,27 +742,26 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
         }
       }
     } else {
-      for (const [iteratedEvseId, evseStatus] of chargingStation.evses) {
-        if (iteratedEvseId === 0) {
-          continue
-        }
-        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-          if (connectorStatus.transactionId != null) {
-            terminationPromises.push(
-              OCPP20ServiceUtils.requestStopTransaction(
-                chargingStation,
-                connectorId,
-                iteratedEvseId,
-                triggerReason,
-                stoppedReason
-              ).catch((error: unknown) => {
-                logger.error(
-                  `${chargingStation.logPrefix()} ${moduleName}.stopAllTransactions: Error stopping transaction on connector ${connectorId.toString()}:`,
-                  error
-                )
-              })
-            )
-          }
+      for (const {
+        connectorId,
+        connectorStatus,
+        evseId: connectorEvseId,
+      } of chargingStation.iterateConnectors(true)) {
+        if (connectorStatus.transactionId != null) {
+          terminationPromises.push(
+            OCPP20ServiceUtils.requestStopTransaction(
+              chargingStation,
+              connectorId,
+              connectorEvseId,
+              triggerReason,
+              stoppedReason
+            ).catch((error: unknown) => {
+              logger.error(
+                `${chargingStation.logPrefix()} ${moduleName}.stopAllTransactions: Error stopping transaction on connector ${connectorId.toString()}:`,
+                error
+              )
+            })
+          )
         }
       }
     }
index 414f5e8fb7f6186921c0609c8c307bf03af5a860..cd95c2587a0a1d5cca23dffd48e2215393b94d0b 100644 (file)
@@ -522,33 +522,9 @@ export const stopRunningTransactions = async (
     case OCPPVersion.VERSION_16: {
       const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
       // Sequential — OCPP 1.6 behavior
-      if (chargingStation.hasEvses) {
-        for (const [evseId, evseStatus] of chargingStation.evses) {
-          if (evseId === 0) {
-            continue
-          }
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            if (connectorStatus.transactionStarted === true) {
-              await OCPP16ServiceUtils.stopTransactionOnConnector(
-                chargingStation,
-                connectorId,
-                reason
-              )
-            }
-          }
-        }
-      } else {
-        for (const connectorId of chargingStation.connectors.keys()) {
-          if (
-            connectorId > 0 &&
-            chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true
-          ) {
-            await OCPP16ServiceUtils.stopTransactionOnConnector(
-              chargingStation,
-              connectorId,
-              reason
-            )
-          }
+      for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors(true)) {
+        if (connectorStatus.transactionStarted === true) {
+          await OCPP16ServiceUtils.stopTransactionOnConnector(chargingStation, connectorId, reason)
         }
       }
       break
@@ -623,35 +599,16 @@ export const flushQueuedTransactionMessages = async (
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201: {
       const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      if (chargingStation.hasEvses) {
-        for (const evseStatus of chargingStation.evses.values()) {
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            if ((connectorStatus.transactionEventQueue?.length ?? 0) > 0) {
-              await OCPP20ServiceUtils.sendQueuedTransactionEvents(
-                chargingStation,
-                connectorId
-              ).catch((error: unknown) => {
-                logger.error(
-                  `${chargingStation.logPrefix()} OCPPServiceUtils.flushQueuedTransactionMessages: Error flushing queued TransactionEvents:`,
-                  error
-                )
-              })
-            }
-          }
-        }
-      } else {
-        for (const [connectorId, connectorStatus] of chargingStation.connectors) {
-          if ((connectorStatus.transactionEventQueue?.length ?? 0) > 0) {
-            await OCPP20ServiceUtils.sendQueuedTransactionEvents(
-              chargingStation,
-              connectorId
-            ).catch((error: unknown) => {
+      for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors()) {
+        if ((connectorStatus.transactionEventQueue?.length ?? 0) > 0) {
+          await OCPP20ServiceUtils.sendQueuedTransactionEvents(chargingStation, connectorId).catch(
+            (error: unknown) => {
               logger.error(
                 `${chargingStation.logPrefix()} OCPPServiceUtils.flushQueuedTransactionMessages: Error flushing queued TransactionEvents:`,
                 error
               )
-            })
-          }
+            }
+          )
         }
       }
       break
index 4a4806b789da50c82fd955563c04dc6873578410..551c1f70f8cee51bf06eabed72356348fb62ad6c 100644 (file)
@@ -7,9 +7,9 @@ import type {
 } from './AutomaticTransactionGenerator.js'
 import type { ChargingStationInfo } from './ChargingStationInfo.js'
 import type { ChargingStationOcppConfiguration } from './ChargingStationOcppConfiguration.js'
-import type { ConnectorStatus } from './ConnectorStatus.js'
+import type { ConnectorEntry } from './ConnectorStatus.js'
+import type { EvseEntry } from './Evse.js'
 import type { JsonObject } from './JsonType.js'
-import type { AvailabilityType } from './ocpp/Requests.js'
 import type { BootNotificationResponse } from './ocpp/Responses.js'
 import type { Statistics } from './Statistics.js'
 import type { UUIDv4 } from './UUID.js'
@@ -79,14 +79,3 @@ export const ChargingStationWorkerMessageEvents = {
 export type ChargingStationWorkerMessageEvents =
   | ChargingStationEvents
   | ChargingStationMessageEvents
-
-export interface ConnectorEntry {
-  connector: ConnectorStatus
-  connectorId: number
-}
-
-export interface EvseEntry {
-  availability: AvailabilityType
-  connectors: ConnectorEntry[]
-  evseId: number
-}
index 56fb49138e59aa90bf3ffce478f51c8c70a72ac9..ec846b3685d91fdbfc1e33d5460eadd5d11e57aa 100644 (file)
@@ -7,6 +7,12 @@ import type { MeterValue } from './ocpp/MeterValues.js'
 import type { AvailabilityType } from './ocpp/Requests.js'
 import type { Reservation } from './ocpp/Reservation.js'
 
+export interface ConnectorEntry {
+  readonly connectorId: number
+  readonly connectorStatus: ConnectorStatus
+  readonly evseId: number | undefined
+}
+
 export interface ConnectorStatus {
   authorizeIdTag?: string
   availability: AvailabilityType
index e1e36b02a336cad8fa367b101d8bca15a244454a..1421a20b99b3a42900761da8fea4dbafb1776aaa 100644 (file)
@@ -2,6 +2,11 @@ import type { ConnectorStatus } from './ConnectorStatus.js'
 import type { SampledValueTemplate } from './MeasurandPerPhaseSampledValueTemplates.js'
 import type { AvailabilityType } from './ocpp/Requests.js'
 
+export interface EvseEntry {
+  readonly evseId: number
+  readonly evseStatus: EvseStatus
+}
+
 export interface EvseStatus {
   availability: AvailabilityType
   connectors: Map<number, ConnectorStatus>
index 2fb0f64e63b232e56f189a4d7193ffa631381394..aa893493e0431380932c04eab01ea94a4449741c 100644 (file)
@@ -32,8 +32,6 @@ export {
   type ChargingStationWorkerMessage,
   type ChargingStationWorkerMessageData,
   ChargingStationWorkerMessageEvents,
-  type ConnectorEntry,
-  type EvseEntry,
 } from './ChargingStationWorker.js'
 export {
   ApplicationProtocolVersion,
@@ -47,10 +45,10 @@ export {
   type UIServerConfiguration,
   type WorkerConfiguration,
 } from './ConfigurationData.js'
-export type { ConnectorStatus } from './ConnectorStatus.js'
+export type { ConnectorEntry, ConnectorStatus } from './ConnectorStatus.js'
 export type { EmptyObject } from './EmptyObject.js'
 export type { HandleErrorParams } from './Error.js'
-export type { EvseStatus, EvseTemplate } from './Evse.js'
+export type { EvseEntry, EvseStatus, EvseTemplate } from './Evse.js'
 export { FileType } from './FileType.js'
 export type { JsonObject, JsonType } from './JsonType.js'
 export { MapStringifyFormat } from './MapStringifyFormat.js'
index a21e28a3a61bcf8cfeefee6153d29d82fbd51a68..e9fd082a7cacdeb7d0d62b9ac90d4d85a1ed5a13 100644 (file)
@@ -31,85 +31,105 @@ export const buildChargingStationAutomaticTransactionGeneratorConfiguration = (
 }
 
 export const buildConnectorEntries = (chargingStation: ChargingStation): ConnectorEntry[] => {
-  return [...chargingStation.connectors.entries()].map(
-    ([
-      connectorId,
-      {
-        transactionEndedMeterValues,
-        transactionEndedMeterValuesSetInterval,
-        transactionEventQueue,
-        transactionUpdatedMeterValuesSetInterval,
-        ...connector
-      },
-    ]) => ({
-      connector,
-      connectorId,
-    })
-  )
-}
-
-export const buildConnectorsStatus = (
-  chargingStation: ChargingStation
-): [number, ConnectorStatus][] => {
-  return [...chargingStation.connectors.entries()].map(
-    ([
-      connectorId,
-      {
-        transactionEndedMeterValues,
-        transactionEndedMeterValuesSetInterval,
-        transactionEventQueue,
-        transactionUpdatedMeterValuesSetInterval,
-        ...connectorStatus
-      },
-    ]) => [connectorId, connectorStatus]
-  )
-}
-
-export const buildEvseEntries = (chargingStation: ChargingStation): EvseEntry[] => {
-  return [...chargingStation.evses.entries()].map(([evseId, evseStatus]) => ({
-    availability: evseStatus.availability,
-    connectors: [...evseStatus.connectors.entries()].map(
-      ([
+  if (chargingStation.hasEvses) {
+    return []
+  }
+  return chargingStation
+    .iterateConnectors()
+    .map(
+      ({
         connectorId,
-        {
+        connectorStatus: {
           transactionEndedMeterValues,
           transactionEndedMeterValuesSetInterval,
           transactionEventQueue,
           transactionUpdatedMeterValuesSetInterval,
-          ...connector
+          ...connectorStatus
         },
-      ]) => ({
-        connector,
+      }) => ({
         connectorId,
+        connectorStatus,
+        evseId: undefined,
       })
-    ),
-    evseId,
-  }))
+    )
+    .toArray()
 }
 
-export const buildEvsesStatus = (
+export const buildConnectorsStatus = (
   chargingStation: ChargingStation
-): [number, EvseStatusConfiguration][] => {
-  return [...chargingStation.evses.entries()].map(([evseId, evseStatus]) => {
-    const connectorsStatus: [number, ConnectorStatus][] = [...evseStatus.connectors.entries()].map(
-      ([
+): [number, ConnectorStatus][] => {
+  if (chargingStation.hasEvses) {
+    return []
+  }
+  return chargingStation
+    .iterateConnectors()
+    .map(
+      ({
         connectorId,
-        {
+        connectorStatus: {
           transactionEndedMeterValues,
           transactionEndedMeterValuesSetInterval,
           transactionEventQueue,
           transactionUpdatedMeterValuesSetInterval,
-          ...connector
+          ...connectorStatus
         },
-      ]) => [connectorId, connector]
+      }) => [connectorId, connectorStatus] as [number, ConnectorStatus]
     )
-    const { connectors: _, ...evseStatusRest } = evseStatus
-    return [
+    .toArray()
+}
+
+export const buildEvseEntries = (chargingStation: ChargingStation): EvseEntry[] => {
+  return chargingStation
+    .iterateEvses()
+    .map(({ evseId, evseStatus }) => ({
       evseId,
-      {
-        ...evseStatusRest,
-        connectorsStatus,
-      } as EvseStatusConfiguration,
-    ]
-  })
+      evseStatus: {
+        availability: evseStatus.availability,
+        connectors: [...evseStatus.connectors.entries()].map(
+          ([
+            connectorId,
+            {
+              transactionEndedMeterValues,
+              transactionEndedMeterValuesSetInterval,
+              transactionEventQueue,
+              transactionUpdatedMeterValuesSetInterval,
+              ...connectorStatus
+            },
+          ]) => ({ connectorId, connectorStatus, evseId })
+        ),
+      },
+    }))
+    .toArray() as unknown as EvseEntry[]
+}
+
+export const buildEvsesStatus = (
+  chargingStation: ChargingStation
+): [number, EvseStatusConfiguration][] => {
+  return chargingStation
+    .iterateEvses()
+    .map(({ evseId, evseStatus }) => {
+      const connectorsStatus: [number, ConnectorStatus][] = [
+        ...evseStatus.connectors.entries(),
+      ].map(
+        ([
+          connectorId,
+          {
+            transactionEndedMeterValues,
+            transactionEndedMeterValuesSetInterval,
+            transactionEventQueue,
+            transactionUpdatedMeterValuesSetInterval,
+            ...connector
+          },
+        ]) => [connectorId, connector]
+      )
+      const { connectors: _, ...evseStatusRest } = evseStatus
+      return [
+        evseId,
+        {
+          ...evseStatusRest,
+          connectorsStatus,
+        } as EvseStatusConfiguration,
+      ] as [number, EvseStatusConfiguration]
+    })
+    .toArray()
 }
index 589fa193db48db36e1d764a3c1842ca3f8d8fb15..076f68ff2951ab9ebb7bb290d29b9ead0f758ea6 100644 (file)
@@ -186,8 +186,8 @@ await describe('ChargingStation Lifecycle', async () => {
       await station.delete(false)
 
       // Assert - connectors and evses should be cleared
-      assert.strictEqual(station.connectors.size, 0)
-      assert.strictEqual(station.evses.size, 0)
+      assert.strictEqual(station.getNumberOfConnectors(), 0)
+      assert.strictEqual(station.getNumberOfEvses(), 0)
       assert.strictEqual(station.requests.size, 0)
     })
 
@@ -203,7 +203,7 @@ await describe('ChargingStation Lifecycle', async () => {
 
       // Assert - station should be stopped and cleared
       assert.strictEqual(station.started, false)
-      assert.strictEqual(station.connectors.size, 0)
+      assert.strictEqual(station.getNumberOfConnectors(), 0)
     })
 
     await it('should handle delete operation with pending transactions', async () => {
@@ -227,8 +227,8 @@ await describe('ChargingStation Lifecycle', async () => {
 
       // Assert - Station should be stopped and resources cleared
       assert.strictEqual(station.started, false)
-      assert.strictEqual(station.connectors.size, 0)
-      assert.strictEqual(station.evses.size, 0)
+      assert.strictEqual(station.getNumberOfConnectors(), 0)
+      assert.strictEqual(station.getNumberOfEvses(), 0)
     })
   })
 })
index 40a58ceb53b5083e17fbb1e482734e202b02b41f..a5929b791d51c8cf47172eb98aa1d5a5e208a238 100644 (file)
@@ -118,7 +118,7 @@ await describe('ChargingStation Resilience', async () => {
       mocks.webSocket.simulateMessage('invalid json')
 
       // Assert - Station should still be operational (not crashed)
-      assert.strictEqual(station.connectors.size > 0, true)
+      assert.strictEqual(station.getNumberOfConnectors() > 0, true)
     })
 
     await it('should handle WebSocket error event gracefully', () => {
@@ -196,7 +196,7 @@ await describe('ChargingStation Resilience', async () => {
       mocks.webSocket.simulateClose(1006, 'Server unreachable')
 
       // Assert - Station should remain in valid state
-      assert.strictEqual(station.connectors.size > 0, true)
+      assert.strictEqual(station.getNumberOfConnectors() > 0, true)
       assert.strictEqual(mocks.webSocket.readyState, 3) // CLOSED
     })
 
index c07cae1edd95cff7d36a2246e45f68956c19d2e5..c456c864759e270a99d7eef68ec30175e58c5879 100644 (file)
@@ -40,7 +40,7 @@ await describe('ChargingStation', async () => {
       const station = result.station
 
       assert.notStrictEqual(station, undefined)
-      assert.strictEqual(station.connectors.size > 0, true)
+      assert.strictEqual(station.getNumberOfConnectors() > 0, true)
       assert.notStrictEqual(station.stationInfo, undefined)
 
       cleanupChargingStation(station)
@@ -50,8 +50,8 @@ await describe('ChargingStation', async () => {
       const result = createMockChargingStation({ connectorsCount: 5 })
       const station = result.station
 
-      // 5 connectors + connector 0 = 6 total
-      assert.strictEqual(station.connectors.size, 6)
+      // 5 connectors (excluding connector 0)
+      assert.strictEqual(station.getNumberOfConnectors(), 5)
       assert.strictEqual(station.hasConnector(5), true)
 
       cleanupChargingStation(station)
index a064f3bb4f0da09176ae3318bd1e68972fe2241a..bd3ede27d36a5c3d3e6ca312be1ad99fc85e4bda 100644 (file)
@@ -724,7 +724,7 @@ await describe('Helpers', async () => {
       baseName: TEST_CHARGING_STATION_BASE_NAME,
       connectorsCount: 2,
     })
-    const connectorStatus = chargingStation.connectors.get(1)
+    const connectorStatus = chargingStation.getConnectorStatus(1)
     if (connectorStatus != null) {
       connectorStatus.reservation = createTestReservation(false)
     }
@@ -752,7 +752,7 @@ await describe('Helpers', async () => {
       connectorsCount: 2,
       stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
     })
-    const firstEvse = chargingStation.evses.get(1)
+    const firstEvse = chargingStation.getEvseStatus(1)
     const firstConnector = firstEvse?.connectors.values().next().value
     if (firstConnector != null) {
       firstConnector.reservation = createTestReservation(false)
@@ -769,7 +769,7 @@ await describe('Helpers', async () => {
       connectorsCount: 2,
       stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
     })
-    const firstEvse = chargingStation.evses.get(1)
+    const firstEvse = chargingStation.getEvseStatus(1)
     const firstConnector = firstEvse?.connectors.values().next().value
     if (firstConnector != null) {
       firstConnector.reservation = createTestReservation(true)
index d4fb945556ef2c27fc3340c615ba5336be87a01b..c0d5813af62de8248ddbd4b0801deb99788eed8a 100644 (file)
@@ -9,7 +9,9 @@ import type {
   ChargingStationInfo,
   ChargingStationOcppConfiguration,
   ChargingStationTemplate,
+  ConnectorEntry,
   ConnectorStatus,
+  EvseEntry,
   EvseStatus,
   Reservation,
   StopTransactionReason,
@@ -217,7 +219,7 @@ export function cleanupChargingStation (station: ChargingStation): void {
   }
 
   // Clear connector transaction state and timers
-  for (const connectorStatus of station.connectors.values()) {
+  for (const { connectorStatus } of station.iterateConnectors()) {
     if (connectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
       clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
       connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
@@ -229,7 +231,7 @@ export function cleanupChargingStation (station: ChargingStation): void {
   }
 
   // Clear EVSE connector transaction state and timers
-  for (const evseStatus of station.evses.values()) {
+  for (const { evseStatus } of station.iterateEvses()) {
     for (const connectorStatus of evseStatus.connectors.values()) {
       if (connectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
         clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
@@ -303,7 +305,7 @@ export function createConnectorStatus (
  * @example
  * ```typescript
  * const { station, mocks } = createMockChargingStation({ connectorsCount: 2 })
- * expect(station.connectors.size).toBe(3) // 0 + 2 connectors
+ * station.getNumberOfConnectors() // 2 (excludes connector 0)
  * station.wsConnection = mocks.webSocket
  * mocks.webSocket.simulateMessage('["3","uuid",{}]')
  * ```
@@ -644,6 +646,10 @@ export function createMockChargingStation (
       return connectors.has(connectorId)
     },
 
+    hasEvse (evseId: number): boolean {
+      return evses.has(evseId)
+    },
+
     // Getters
     get hasEvses (): boolean {
       return useEvses
@@ -694,6 +700,30 @@ export function createMockChargingStation (
       return this.wsConnection?.readyState === WebSocketReadyState.OPEN
     },
 
+    * iterateConnectors (skipZero = false): Generator<ConnectorEntry> {
+      if (useEvses) {
+        for (const [evseId, evseStatus] of evses) {
+          if (skipZero && evseId === 0) continue
+          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+            if (skipZero && connectorId === 0) continue
+            yield { connectorId, connectorStatus, evseId }
+          }
+        }
+      } else {
+        for (const [connectorId, connectorStatus] of connectors) {
+          if (skipZero && connectorId === 0) continue
+          yield { connectorId, connectorStatus, evseId: undefined }
+        }
+      }
+    },
+
+    * iterateEvses (skipZero = false): Generator<EvseEntry> {
+      for (const [evseId, evseStatus] of evses) {
+        if (skipZero && evseId === 0) continue
+        yield { evseId, evseStatus }
+      }
+    },
+
     listenerCount: () => 0,
 
     lockConnector (connectorId: number): void {
@@ -923,12 +953,12 @@ export function resetChargingStationState (station: ChargingStation): void {
   }
 
   // Reset connector statuses
-  for (const [connectorId, connectorStatus] of station.connectors) {
+  for (const { connectorId, connectorStatus } of station.iterateConnectors()) {
     resetConnectorStatus(connectorStatus, connectorId === 0)
   }
 
   // Reset EVSE connector statuses
-  for (const evseStatus of station.evses.values()) {
+  for (const { evseStatus } of station.iterateEvses()) {
     evseStatus.availability = AvailabilityType.Operative
     for (const connectorStatus of evseStatus.connectors.values()) {
       resetConnectorStatus(connectorStatus, false)
index c69dd972e4533753566a949b491b2435d67025fb..51236ab2e1eeed029fa079b7dfaa5b31c2e67ca4 100644 (file)
@@ -213,7 +213,7 @@ await describe('OCPP16IncomingRequestService — SmartCharging', async () => {
         'Core,SmartCharging'
       )
       // Ensure no profiles on any connector
-      for (const [connectorId] of station.connectors.entries()) {
+      for (const { connectorId } of station.iterateConnectors()) {
         const connectorStatus = station.getConnectorStatus(connectorId)
         if (connectorStatus != null) {
           connectorStatus.chargingProfiles = []
index d100bdd90309c34df38b0723a78fdd3c8709d8a3..0c6fe70b8ecfc27b473ad07542ae16e119a31e1c 100644 (file)
@@ -91,12 +91,10 @@ function createIntegrationContext (): {
   )
 
   // Add MeterValues template required by buildTransactionBeginMeterValue
-  for (const [connectorId] of station.connectors) {
-    if (connectorId > 0) {
-      const connectorStatus = station.getConnectorStatus(connectorId)
-      if (connectorStatus != null) {
-        connectorStatus.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
-      }
+  for (const { connectorId } of station.iterateConnectors(true)) {
+    const connectorStatus = station.getConnectorStatus(connectorId)
+    if (connectorStatus != null) {
+      connectorStatus.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
     }
   }
 
index a59a7f8b8e4394b7b1ab163e090348c661dab0be..d389eaa44832f494d635065ce4fca3f31869d60f 100644 (file)
@@ -50,12 +50,10 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
     })
 
     // Add MeterValues template required by buildTransactionBeginMeterValue
-    for (const [connectorId] of station.connectors) {
-      if (connectorId > 0) {
-        const connectorStatus = station.getConnectorStatus(connectorId)
-        if (connectorStatus != null) {
-          connectorStatus.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
-        }
+    for (const { connectorId } of station.iterateConnectors(true)) {
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus != null) {
+        connectorStatus.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
       }
     }
   })
index 4bee624af3dd0b1906db060b36f3ffcefb93d8e2..f435e3de59c003ae86e5fa4923f18fd82b6e3b99 100644 (file)
@@ -298,8 +298,7 @@ export async function dispatchResponse (
  * @param chargingStation - Charging station instance whose connector state should be reset
  */
 export function resetConnectorTransactionState (chargingStation: ChargingStation): void {
-  for (const [connectorId, connectorStatus] of chargingStation.connectors.entries()) {
-    if (connectorId === 0) continue
+  for (const { connectorStatus } of chargingStation.iterateConnectors(true)) {
     connectorStatus.transactionStarted = false
     connectorStatus.transactionId = undefined
     connectorStatus.transactionIdTag = undefined
index b64ceabb81bc840d7494e772a6022ea4167907ad..190ee6f0a330ad4b2191125b245cf9a1a309f00c 100644 (file)
@@ -71,14 +71,12 @@ await describe('G03 - ChangeAvailability', async () => {
     })
 
     assert.strictEqual(response.status, ChangeAvailabilityStatusEnumType.Accepted)
-    for (const [evseId, evseStatus] of station.evses) {
-      if (evseId > 0) {
-        assert.strictEqual(
-          evseStatus.availability,
-          OCPP20OperationalStatusEnumType.Inoperative,
-          `EVSE ${String(evseId)} should be Inoperative`
-        )
-      }
+    for (const { evseId, evseStatus } of station.iterateEvses(true)) {
+      assert.strictEqual(
+        evseStatus.availability,
+        OCPP20OperationalStatusEnumType.Inoperative,
+        `EVSE ${String(evseId)} should be Inoperative`
+      )
     }
   })
 
@@ -157,10 +155,8 @@ await describe('G03 - ChangeAvailability', async () => {
     })
 
     assert.strictEqual(response.status, ChangeAvailabilityStatusEnumType.Accepted)
-    for (const [evseId, evseStatus] of station.evses) {
-      if (evseId > 0) {
-        assert.strictEqual(evseStatus.availability, OCPP20OperationalStatusEnumType.Inoperative)
-      }
+    for (const { evseStatus } of station.iterateEvses(true)) {
+      assert.strictEqual(evseStatus.availability, OCPP20OperationalStatusEnumType.Inoperative)
     }
   })
 })
index 8df12ffadb882197f66165e88c340f3cc5d1a1f7..6c46f17b89e32a4306692903442e309ca123699a 100644 (file)
@@ -22,43 +22,33 @@ import {
   RequestStartStopStatusEnumType,
 } from '../../../../src/types/index.js'
 import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+  cleanupChargingStation,
+  createMockChargingStation,
+} from '../../ChargingStationTestUtils.js'
 
 await describe('G03 - Remote Start Pre-Authorization', async () => {
   let service: OCPP20IncomingRequestService | undefined
   let mockStation: ChargingStation | undefined
 
   beforeEach(() => {
-    // Mock charging station with EVSE configuration
-    mockStation = {
-      evses: new Map([
-        [
-          1,
-          {
-            connectors: new Map([[1, { status: ConnectorStatusEnum.Available }]]),
-          },
-        ],
-      ]),
-      getConnectorStatus: (_connectorId: number): ConnectorStatus => ({
-        availability: OCPP20OperationalStatusEnumType.Operative,
-        MeterValues: [],
-        status: ConnectorStatusEnum.Available,
-        transactionId: undefined,
-        transactionIdTag: undefined,
-        transactionStart: undefined,
-        transactionStarted: false,
-      }),
-      inAcceptedState: () => true,
-      logPrefix: () => '[TEST-STATION-REMOTE-START]',
+    const { station } = createMockChargingStation({
+      connectorsCount: 1,
+      evseConfiguration: { evsesCount: 1 },
+      ocppVersion: OCPPVersion.VERSION_201,
       stationInfo: {
         chargingStationId: 'TEST-REMOTE-START',
-        ocppVersion: OCPPVersion.VERSION_201,
       },
-    } as unknown as ChargingStation
+    })
+    mockStation = station
 
     service = new OCPP20IncomingRequestService()
   })
 
   afterEach(() => {
+    if (mockStation != null) {
+      cleanupChargingStation(mockStation)
+    }
     standardCleanup()
     mockStation = undefined
     service = undefined
@@ -431,8 +421,8 @@ await describe('G03 - Remote Start Pre-Authorization', async () => {
       assert(mockStation != null)
       // Then: Charging station should have required configuration
       assert.notStrictEqual(mockStation, undefined)
-      assert.notStrictEqual(mockStation.evses, undefined)
-      assert.ok(mockStation.evses.size > 0)
+      assert.notStrictEqual(mockStation.getNumberOfEvses(), 0)
+      assert.ok(mockStation.getNumberOfEvses() > 0)
       assert.strictEqual(mockStation.stationInfo?.ocppVersion, OCPPVersion.VERSION_201)
     })
   })
index 51f022224519e2edf17cc08bfaf436a86a0db39f..058ecff9a161b082a491f43dd090cc2d9b7d427a 100644 (file)
@@ -13,6 +13,7 @@ import type {
   OCPP20ChargingRateUnitEnumType,
   OCPP20RequestStartTransactionRequest,
   OCPP20RequestStartTransactionResponse,
+  OCPP20TransactionEventOptions,
   OCPP20TransactionEventRequest,
 } from '../../../../src/types/index.js'
 
@@ -521,6 +522,11 @@ await describe('F01 & F02 - Remote Start Transaction', async () => {
       ]
       const transactionEvent = args[2]
       assert.strictEqual(transactionEvent.triggerReason, OCPP20TriggerReasonEnumType.RemoteStart)
+      // F01.FR.25: remoteStartId SHALL be included in TransactionEventRequest
+      assert.strictEqual(
+        (transactionEvent as unknown as OCPP20TransactionEventOptions).remoteStartId,
+        3
+      )
     })
 
     await it('should handle TransactionEvent failure gracefully', async () => {
index 6ce2be69ff5c1578ea17d3732691005c3118e34d..0bfd679c48d33e8c59c6812515ddc971284536ce 100644 (file)
@@ -436,7 +436,7 @@ await describe('B11 & B12 - Reset', async () => {
           }
 
           // Assign reservation to first connector
-          const evse: EvseStatus | undefined = station.evses.get(1)
+          const evse: EvseStatus | undefined = station.getEvseStatus(1)
           if (evse) {
             const connectorId = [...evse.connectors.keys()][0]
             const connectorStatus = evse.connectors.get(connectorId)
@@ -469,7 +469,7 @@ await describe('B11 & B12 - Reset', async () => {
           }
 
           // Assign expired reservation to first connector
-          const evse: EvseStatus | undefined = station.evses.get(1)
+          const evse: EvseStatus | undefined = station.getEvseStatus(1)
           if (evse) {
             const connectorId = [...evse.connectors.keys()][0]
             const connectorStatus = evse.connectors.get(connectorId)
@@ -550,7 +550,7 @@ await describe('B11 & B12 - Reset', async () => {
             id: 1,
             idTag: 'test-tag',
           }
-          const evse: EvseStatus | undefined = station.evses.get(1)
+          const evse: EvseStatus | undefined = station.getEvseStatus(1)
           if (evse) {
             const connectorId = [...evse.connectors.keys()][0]
             const connectorStatus = evse.connectors.get(connectorId)
index 936f3cba107e746a122c64f14ee58f4ba2a93556..8a5f851ebf740a55795c02304bce9842b30b49e5 100644 (file)
@@ -137,7 +137,7 @@ await describe('F05 - UnlockConnector', async () => {
     await it('should return OngoingAuthorizedTransaction when specified connector has active transaction', async () => {
       const { mockStation } = createUnlockConnectorStation()
 
-      const evseStatus = mockStation.evses.get(1)
+      const evseStatus = mockStation.getEvseStatus(1)
       const connectorStatus = evseStatus?.connectors.get(1)
       if (connectorStatus != null) {
         connectorStatus.transactionId = 'tx-001'
@@ -175,7 +175,7 @@ await describe('F05 - UnlockConnector', async () => {
       })
       const multiConnectorStation = station as MockChargingStation
 
-      const evseStatus = multiConnectorStation.evses.get(1)
+      const evseStatus = multiConnectorStation.getEvseStatus(1)
       const connector2 = evseStatus?.connectors.get(2)
       if (connector2 != null) {
         connector2.transactionId = 'tx-other'
index 121a0f691846e62b8e5fc13a7d6b7dc6bc8ba455..f3f83a5b1e8ae5852540b4d5a6906fac42d19cd4 100644 (file)
@@ -174,7 +174,7 @@ await describe('L01/L02 - UpdateFirmware', async () => {
       })
 
       // Set an active transaction on EVSE 1's connector
-      const evse1 = evseStation.evses.get(1)
+      const evse1 = evseStation.getEvseStatus(1)
       if (evse1 != null) {
         const firstConnector = evse1.connectors.values().next().value
         if (firstConnector != null) {
index a346c07dc2407252443dd521b69148346ddb6e8e..3ceed9d931adba9dd2e1a5a745d7f5fe68b9f1c1 100644 (file)
@@ -225,7 +225,7 @@ export function createTestableOCPP20RequestService (
  */
 export function resetConnectorTransactionState (chargingStation: ChargingStation): void {
   if (chargingStation.hasEvses) {
-    for (const evseStatus of chargingStation.evses.values()) {
+    for (const { evseStatus } of chargingStation.iterateEvses()) {
       for (const connectorStatus of evseStatus.connectors.values()) {
         connectorStatus.transactionStarted = false
         connectorStatus.transactionId = undefined
@@ -239,8 +239,7 @@ export function resetConnectorTransactionState (chargingStation: ChargingStation
       }
     }
   } else {
-    for (const [connectorId, connectorStatus] of chargingStation.connectors.entries()) {
-      if (connectorId === 0) continue // Skip connector 0 (charging station itself)
+    for (const { connectorStatus } of chargingStation.iterateConnectors(true)) {
       connectorStatus.transactionStarted = false
       connectorStatus.transactionId = undefined
       connectorStatus.transactionIdTag = undefined
index a7625eca9f45ca77e41aa23126ba0dc0dd6be1be..6e2a7ac9a553d7b274c64a563b6fa7bd580c4c37 100644 (file)
@@ -9,7 +9,7 @@ import assert from 'node:assert/strict'
 import { afterEach, describe, it } from 'node:test'
 
 import type { ChargingStation } from '../../src/charging-station/index.js'
-import type { ConnectorStatus, EvseStatus } from '../../src/types/index.js'
+import type { ConnectorEntry, ConnectorStatus, EvseStatus } from '../../src/types/index.js'
 
 import { AvailabilityType } from '../../src/types/index.js'
 import {
@@ -20,34 +20,29 @@ import {
   buildEvseEntries,
   buildEvsesStatus,
 } from '../../src/utils/ChargingStationConfigurationUtils.js'
+import {
+  cleanupChargingStation,
+  createMockChargingStation,
+} from '../charging-station/ChargingStationTestUtils.js'
 import { standardCleanup } from '../helpers/TestLifecycleHelpers.js'
 
 /**
- * Creates a minimal mock ChargingStation for configuration utility tests.
- * @param options - Mock station properties.
- * @param options.automaticTransactionGenerator - ATG instance stub.
- * @param options.connectors - Connectors map.
- * @param options.evses - EVSEs map.
- * @param options.getAutomaticTransactionGeneratorConfiguration - ATG config getter.
- * @returns Partial ChargingStation cast for test use.
+ * Typed access to mock station internals that are private on ChargingStation.
+ * The mock factory creates a plain object where these are regular properties.
  */
-function createMockStationForConfigUtils (options: {
-  automaticTransactionGenerator?: undefined | { connectorsStatus?: Map<number, unknown> }
-  connectors?: Map<number, ConnectorStatus>
-  evses?: Map<number, EvseStatus>
-  getAutomaticTransactionGeneratorConfiguration?: () => unknown
-}): ChargingStation {
-  return {
-    automaticTransactionGenerator: options.automaticTransactionGenerator ?? undefined,
-    connectors: options.connectors ?? new Map(),
-    evses: options.evses ?? new Map(),
-    getAutomaticTransactionGeneratorConfiguration:
-      options.getAutomaticTransactionGeneratorConfiguration ?? (() => undefined),
-  } as unknown as ChargingStation
+interface MockStationInternals {
+  connectors: Map<number, ConnectorStatus>
+  evses: Map<number, EvseStatus>
 }
 
 await describe('ChargingStationConfigurationUtils', async () => {
+  let testStation: ChargingStation | undefined
+
   afterEach(() => {
+    if (testStation != null) {
+      cleanupChargingStation(testStation)
+      testStation = undefined
+    }
     standardCleanup()
   })
 
@@ -61,12 +56,15 @@ await describe('ChargingStationConfigurationUtils', async () => {
       const interval1 = setInterval(noop, 1000)
       const interval2 = setInterval(noop, 1000)
       try {
-        const connectors = new Map<number, ConnectorStatus>()
-        connectors.set(0, {
+        const { station } = createMockChargingStation({ connectorsCount: 0 })
+        testStation = station
+        const internals = station as unknown as MockStationInternals
+        internals.connectors.clear()
+        internals.connectors.set(0, {
           availability: AvailabilityType.Operative,
           MeterValues: [],
         } as ConnectorStatus)
-        connectors.set(1, {
+        internals.connectors.set(1, {
           availability: AvailabilityType.Operative,
           bootStatus: 'Available',
           MeterValues: [],
@@ -76,7 +74,6 @@ await describe('ChargingStationConfigurationUtils', async () => {
           transactionUpdatedMeterValuesSetInterval: interval1 as unknown as NodeJS.Timeout,
         } as unknown as ConnectorStatus)
 
-        const station = createMockStationForConfigUtils({ connectors })
         const result = buildConnectorsStatus(station)
 
         assert.strictEqual(result.length, 2)
@@ -96,14 +93,19 @@ await describe('ChargingStationConfigurationUtils', async () => {
     })
 
     await it('should handle empty connectors map', () => {
-      const station = createMockStationForConfigUtils({ connectors: new Map() })
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      ;(station as unknown as MockStationInternals).connectors.clear()
       const result = buildConnectorsStatus(station)
       assert.strictEqual(result.length, 0)
     })
 
     await it('should preserve non-internal fields', () => {
-      const connectors = new Map<number, ConnectorStatus>()
-      connectors.set(1, {
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.connectors.clear()
+      internals.connectors.set(1, {
         availability: AvailabilityType.Operative,
         bootStatus: 'Available',
         MeterValues: [],
@@ -113,7 +115,6 @@ await describe('ChargingStationConfigurationUtils', async () => {
         transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
-      const station = createMockStationForConfigUtils({ connectors })
       const result = buildConnectorsStatus(station)
 
       assert.strictEqual(result.length, 1)
@@ -124,17 +125,19 @@ await describe('ChargingStationConfigurationUtils', async () => {
     })
 
     await it('should preserve non-sequential connector IDs', () => {
-      const connectors = new Map<number, ConnectorStatus>()
-      connectors.set(0, {
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.connectors.clear()
+      internals.connectors.set(0, {
         availability: AvailabilityType.Operative,
         MeterValues: [],
       } as ConnectorStatus)
-      connectors.set(3, {
+      internals.connectors.set(3, {
         availability: AvailabilityType.Inoperative,
         MeterValues: [],
       } as ConnectorStatus)
 
-      const station = createMockStationForConfigUtils({ connectors })
       const result = buildConnectorsStatus(station)
 
       assert.strictEqual(result.length, 2)
@@ -146,6 +149,14 @@ await describe('ChargingStationConfigurationUtils', async () => {
 
   await describe('buildEvsesStatus', async () => {
     await it('should return configuration format with connectorsStatus and without connectors', () => {
+      const { station } = createMockChargingStation({
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+      })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.evses.clear()
+
       const evseConnectors = new Map<number, ConnectorStatus>()
       evseConnectors.set(1, {
         availability: AvailabilityType.Operative,
@@ -154,17 +165,15 @@ await describe('ChargingStationConfigurationUtils', async () => {
         transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
-      const evses = new Map<number, EvseStatus>()
-      evses.set(0, {
+      internals.evses.set(0, {
         availability: AvailabilityType.Operative,
         connectors: new Map<number, ConnectorStatus>(),
       })
-      evses.set(1, {
+      internals.evses.set(1, {
         availability: AvailabilityType.Operative,
         connectors: evseConnectors,
       })
 
-      const station = createMockStationForConfigUtils({ evses })
       const result = buildEvsesStatus(station)
 
       assert.strictEqual(result.length, 2)
@@ -178,6 +187,14 @@ await describe('ChargingStationConfigurationUtils', async () => {
     })
 
     await it('should strip internal fields from evse connectors', () => {
+      const { station } = createMockChargingStation({
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+      })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.evses.clear()
+
       const evseConnectors = new Map<number, ConnectorStatus>()
       evseConnectors.set(1, {
         availability: AvailabilityType.Operative,
@@ -188,17 +205,15 @@ await describe('ChargingStationConfigurationUtils', async () => {
         transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
-      const evses = new Map<number, EvseStatus>()
-      evses.set(0, {
+      internals.evses.set(0, {
         availability: AvailabilityType.Operative,
         connectors: new Map<number, ConnectorStatus>(),
       })
-      evses.set(1, {
+      internals.evses.set(1, {
         availability: AvailabilityType.Operative,
         connectors: evseConnectors,
       })
 
-      const station = createMockStationForConfigUtils({ evses })
       const result = buildEvsesStatus(station)
       const evse1 = result.find(([id]) => id === 1)?.[1]
       assert.ok(evse1 != null)
@@ -214,6 +229,14 @@ await describe('ChargingStationConfigurationUtils', async () => {
     })
 
     await it('should preserve connector IDs across serialization', () => {
+      const { station } = createMockChargingStation({
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+      })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.evses.clear()
+
       const evseConnectors = new Map<number, ConnectorStatus>()
       evseConnectors.set(1, {
         availability: AvailabilityType.Operative,
@@ -230,17 +253,15 @@ await describe('ChargingStationConfigurationUtils', async () => {
         MeterValues: [],
       } as ConnectorStatus)
 
-      const evses = new Map<number, EvseStatus>()
-      evses.set(0, {
+      internals.evses.set(0, {
         availability: AvailabilityType.Operative,
         connectors: evse0Connectors,
       })
-      evses.set(1, {
+      internals.evses.set(1, {
         availability: AvailabilityType.Operative,
         connectors: evseConnectors,
       })
 
-      const station = createMockStationForConfigUtils({ evses })
       const result = buildEvsesStatus(station)
 
       const evse0Status = result[0][1].connectorsStatus as [number, ConnectorStatus][]
@@ -255,23 +276,33 @@ await describe('ChargingStationConfigurationUtils', async () => {
     })
 
     await it('should handle empty evses map', () => {
-      const station = createMockStationForConfigUtils({ evses: new Map() })
+      const { station } = createMockChargingStation({
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+      })
+      testStation = station
+      ;(station as unknown as MockStationInternals).evses.clear()
       const result = buildEvsesStatus(station)
       assert.strictEqual(result.length, 0)
     })
 
     await it('should preserve non-sequential evse IDs', () => {
-      const evses = new Map<number, EvseStatus>()
-      evses.set(0, {
+      const { station } = createMockChargingStation({
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+      })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.evses.clear()
+      internals.evses.set(0, {
         availability: AvailabilityType.Operative,
         connectors: new Map<number, ConnectorStatus>(),
       })
-      evses.set(3, {
+      internals.evses.set(3, {
         availability: AvailabilityType.Inoperative,
         connectors: new Map<number, ConnectorStatus>(),
       })
 
-      const station = createMockStationForConfigUtils({ evses })
       const result = buildEvsesStatus(station)
 
       assert.strictEqual(result.length, 2)
@@ -284,11 +315,15 @@ await describe('ChargingStationConfigurationUtils', async () => {
   await describe('buildChargingStationAutomaticTransactionGeneratorConfiguration', async () => {
     await it('should return ATG configuration when present', () => {
       const atgConfiguration = { enable: true, maxDuration: 120, minDuration: 60 }
-      const station = createMockStationForConfigUtils({
-        automaticTransactionGenerator: {
-          connectorsStatus: new Map([[1, { start: false }]]),
-        },
-        getAutomaticTransactionGeneratorConfiguration: () => atgConfiguration,
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      station.automaticTransactionGenerator = {
+        connectorsStatus: new Map([[1, { start: false }]]),
+      } as unknown as typeof station.automaticTransactionGenerator
+      Object.defineProperty(station, 'getAutomaticTransactionGeneratorConfiguration', {
+        configurable: true,
+        value: () => atgConfiguration,
+        writable: true,
       })
       const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
 
@@ -300,9 +335,13 @@ await describe('ChargingStationConfigurationUtils', async () => {
 
     await it('should return ATG configuration without statuses when no ATG instance', () => {
       const atgConfiguration = { enable: false }
-      const station = createMockStationForConfigUtils({
-        automaticTransactionGenerator: undefined,
-        getAutomaticTransactionGeneratorConfiguration: () => atgConfiguration,
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      station.automaticTransactionGenerator = undefined
+      Object.defineProperty(station, 'getAutomaticTransactionGeneratorConfiguration', {
+        configurable: true,
+        value: () => atgConfiguration,
+        writable: true,
       })
       const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
 
@@ -311,9 +350,13 @@ await describe('ChargingStationConfigurationUtils', async () => {
     })
 
     await it('should return undefined ATG config when not configured', () => {
-      const station = createMockStationForConfigUtils({
-        automaticTransactionGenerator: undefined,
-        getAutomaticTransactionGeneratorConfiguration: () => undefined,
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      station.automaticTransactionGenerator = undefined
+      Object.defineProperty(station, 'getAutomaticTransactionGeneratorConfiguration', {
+        configurable: true,
+        value: () => undefined,
+        writable: true,
       })
       const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
 
@@ -323,11 +366,15 @@ await describe('ChargingStationConfigurationUtils', async () => {
 
     await it('should return ATG configuration without statuses when connectorsStatus is null', () => {
       const atgConfiguration = { enable: true }
-      const station = createMockStationForConfigUtils({
-        automaticTransactionGenerator: {
-          connectorsStatus: undefined,
-        },
-        getAutomaticTransactionGeneratorConfiguration: () => atgConfiguration,
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      station.automaticTransactionGenerator = {
+        connectorsStatus: undefined,
+      } as unknown as typeof station.automaticTransactionGenerator
+      Object.defineProperty(station, 'getAutomaticTransactionGeneratorConfiguration', {
+        configurable: true,
+        value: () => atgConfiguration,
+        writable: true,
       })
       const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
 
@@ -344,9 +391,11 @@ await describe('ChargingStationConfigurationUtils', async () => {
       connectorsStatus.set(1, { start: true })
       connectorsStatus.set(3, { start: false })
 
-      const station = createMockStationForConfigUtils({
-        automaticTransactionGenerator: { connectorsStatus },
-      })
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      station.automaticTransactionGenerator = {
+        connectorsStatus,
+      } as unknown as typeof station.automaticTransactionGenerator
       const result = buildATGEntries(station)
 
       assert.strictEqual(result.length, 2)
@@ -361,9 +410,11 @@ await describe('ChargingStationConfigurationUtils', async () => {
       connectorsStatus.set(2, { start: true })
       connectorsStatus.set(7, { start: false })
 
-      const station = createMockStationForConfigUtils({
-        automaticTransactionGenerator: { connectorsStatus },
-      })
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      station.automaticTransactionGenerator = {
+        connectorsStatus,
+      } as unknown as typeof station.automaticTransactionGenerator
       const result = buildATGEntries(station)
 
       assert.strictEqual(result.length, 2)
@@ -372,30 +423,35 @@ await describe('ChargingStationConfigurationUtils', async () => {
     })
 
     await it('should return empty array when no ATG instance', () => {
-      const station = createMockStationForConfigUtils({
-        automaticTransactionGenerator: undefined,
-      })
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      station.automaticTransactionGenerator = undefined
       const result = buildATGEntries(station)
       assert.strictEqual(result.length, 0)
     })
 
     await it('should return empty array when connectorsStatus is undefined', () => {
-      const station = createMockStationForConfigUtils({
-        automaticTransactionGenerator: { connectorsStatus: undefined },
-      })
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      station.automaticTransactionGenerator = {
+        connectorsStatus: undefined,
+      } as unknown as typeof station.automaticTransactionGenerator
       const result = buildATGEntries(station)
       assert.strictEqual(result.length, 0)
     })
   })
 
   await describe('buildConnectorEntries', async () => {
-    await it('should return entries with connectorId and stripped connector', () => {
-      const connectors = new Map<number, ConnectorStatus>()
-      connectors.set(0, {
+    await it('should return entries with connectorId, connectorStatus, and evseId', () => {
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.connectors.clear()
+      internals.connectors.set(0, {
         availability: AvailabilityType.Operative,
         MeterValues: [],
       } as ConnectorStatus)
-      connectors.set(1, {
+      internals.connectors.set(1, {
         availability: AvailabilityType.Operative,
         MeterValues: [],
         transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
@@ -404,53 +460,66 @@ await describe('ChargingStationConfigurationUtils', async () => {
         transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
-      const station = createMockStationForConfigUtils({ connectors })
       const result = buildConnectorEntries(station)
 
       assert.strictEqual(result.length, 2)
       assert.strictEqual(result[0].connectorId, 0)
+      assert.strictEqual(result[0].evseId, undefined)
       assert.strictEqual(result[1].connectorId, 1)
-      assert.strictEqual(result[1].connector.availability, AvailabilityType.Operative)
-      assert.ok(!('transactionEndedMeterValues' in result[1].connector))
-      assert.ok(!('transactionEndedMeterValuesSetInterval' in result[1].connector))
-      assert.ok(!('transactionUpdatedMeterValuesSetInterval' in result[1].connector))
-      assert.ok(!('transactionEventQueue' in result[1].connector))
+      assert.strictEqual(result[1].evseId, undefined)
+      assert.strictEqual(result[1].connectorStatus.availability, AvailabilityType.Operative)
+      assert.ok(!('transactionEndedMeterValues' in result[1].connectorStatus))
+      assert.ok(!('transactionEndedMeterValuesSetInterval' in result[1].connectorStatus))
+      assert.ok(!('transactionUpdatedMeterValuesSetInterval' in result[1].connectorStatus))
+      assert.ok(!('transactionEventQueue' in result[1].connectorStatus))
     })
 
     await it('should handle empty connectors map', () => {
-      const station = createMockStationForConfigUtils({ connectors: new Map() })
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      ;(station as unknown as MockStationInternals).connectors.clear()
       const result = buildConnectorEntries(station)
       assert.strictEqual(result.length, 0)
     })
 
     await it('should preserve non-sequential connector IDs', () => {
-      const connectors = new Map<number, ConnectorStatus>()
-      connectors.set(0, {
+      const { station } = createMockChargingStation({ connectorsCount: 0 })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.connectors.clear()
+      internals.connectors.set(0, {
         availability: AvailabilityType.Operative,
         MeterValues: [],
       } as ConnectorStatus)
-      connectors.set(3, {
+      internals.connectors.set(3, {
         availability: AvailabilityType.Operative,
         MeterValues: [],
       } as ConnectorStatus)
-      connectors.set(7, {
+      internals.connectors.set(7, {
         availability: AvailabilityType.Inoperative,
         MeterValues: [],
       } as ConnectorStatus)
 
-      const station = createMockStationForConfigUtils({ connectors })
       const result = buildConnectorEntries(station)
 
       assert.strictEqual(result.length, 3)
       assert.strictEqual(result[0].connectorId, 0)
       assert.strictEqual(result[1].connectorId, 3)
       assert.strictEqual(result[2].connectorId, 7)
-      assert.strictEqual(result[2].connector.availability, AvailabilityType.Inoperative)
+      assert.strictEqual(result[2].connectorStatus.availability, AvailabilityType.Inoperative)
     })
   })
 
   await describe('buildEvseEntries', async () => {
-    await it('should return entries with evseId, availability, and connector entries', () => {
+    await it('should return entries with evseId, evseStatus containing availability and connectors Map', () => {
+      const { station } = createMockChargingStation({
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+      })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.evses.clear()
+
       const evseConnectors = new Map<number, ConnectorStatus>()
       evseConnectors.set(1, {
         availability: AvailabilityType.Operative,
@@ -461,39 +530,51 @@ await describe('ChargingStationConfigurationUtils', async () => {
         transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
-      const evses = new Map<number, EvseStatus>()
-      evses.set(0, {
+      internals.evses.set(0, {
         availability: AvailabilityType.Operative,
         connectors: new Map<number, ConnectorStatus>(),
       })
-      evses.set(1, {
+      internals.evses.set(1, {
         availability: AvailabilityType.Operative,
         connectors: evseConnectors,
       })
 
-      const station = createMockStationForConfigUtils({ evses })
       const result = buildEvseEntries(station)
 
       assert.strictEqual(result.length, 2)
       assert.strictEqual(result[0].evseId, 0)
-      assert.strictEqual(result[0].availability, AvailabilityType.Operative)
-      assert.strictEqual(result[0].connectors.length, 0)
+      assert.strictEqual(result[0].evseStatus.availability, AvailabilityType.Operative)
+      assert.strictEqual((result[0].evseStatus.connectors as unknown as ConnectorEntry[]).length, 0)
       assert.strictEqual(result[1].evseId, 1)
-      assert.strictEqual(result[1].connectors.length, 1)
-      assert.strictEqual(result[1].connectors[0].connectorId, 1)
-      assert.ok(!('transactionEndedMeterValues' in result[1].connectors[0].connector))
-      assert.ok(!('transactionEndedMeterValuesSetInterval' in result[1].connectors[0].connector))
-      assert.ok(!('transactionUpdatedMeterValuesSetInterval' in result[1].connectors[0].connector))
-      assert.ok(!('transactionEventQueue' in result[1].connectors[0].connector))
+      const connectors1 = result[1].evseStatus.connectors as unknown as ConnectorEntry[]
+      assert.strictEqual(connectors1.length, 1)
+      assert.strictEqual(connectors1[0].connectorId, 1)
+      assert.ok(!('transactionEndedMeterValues' in connectors1[0].connectorStatus))
+      assert.ok(!('transactionEndedMeterValuesSetInterval' in connectors1[0].connectorStatus))
+      assert.ok(!('transactionUpdatedMeterValuesSetInterval' in connectors1[0].connectorStatus))
+      assert.ok(!('transactionEventQueue' in connectors1[0].connectorStatus))
     })
 
     await it('should handle empty evses map', () => {
-      const station = createMockStationForConfigUtils({ evses: new Map() })
+      const { station } = createMockChargingStation({
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+      })
+      testStation = station
+      ;(station as unknown as MockStationInternals).evses.clear()
       const result = buildEvseEntries(station)
       assert.strictEqual(result.length, 0)
     })
 
     await it('should preserve non-sequential evseId and connectorId', () => {
+      const { station } = createMockChargingStation({
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+      })
+      testStation = station
+      const internals = station as unknown as MockStationInternals
+      internals.evses.clear()
+
       const evse2Connectors = new Map<number, ConnectorStatus>()
       evse2Connectors.set(2, {
         availability: AvailabilityType.Operative,
@@ -504,29 +585,26 @@ await describe('ChargingStationConfigurationUtils', async () => {
         MeterValues: [],
       } as ConnectorStatus)
 
-      const evses = new Map<number, EvseStatus>()
-      evses.set(0, {
+      internals.evses.set(0, {
         availability: AvailabilityType.Operative,
         connectors: new Map<number, ConnectorStatus>(),
       })
-      evses.set(3, {
+      internals.evses.set(3, {
         availability: AvailabilityType.Operative,
         connectors: evse2Connectors,
       })
 
-      const station = createMockStationForConfigUtils({ evses })
       const result = buildEvseEntries(station)
 
       assert.strictEqual(result.length, 2)
       assert.strictEqual(result[0].evseId, 0)
       assert.strictEqual(result[1].evseId, 3)
-      assert.strictEqual(result[1].connectors.length, 2)
-      assert.strictEqual(result[1].connectors[0].connectorId, 2)
-      assert.strictEqual(result[1].connectors[1].connectorId, 5)
-      assert.strictEqual(
-        result[1].connectors[1].connector.availability,
-        AvailabilityType.Inoperative
-      )
+      const connectors3 = result[1].evseStatus.connectors as unknown as ConnectorEntry[]
+      assert.strictEqual(connectors3.length, 2)
+      assert.ok(connectors3.some(c => c.connectorId === 2))
+      assert.ok(connectors3.some(c => c.connectorId === 5))
+      const conn5 = connectors3.find(c => c.connectorId === 5)
+      assert.strictEqual(conn5?.connectorStatus.availability, AvailabilityType.Inoperative)
     })
   })
 })
index 7e2be5ae078a5e02fb3202c372e1555acfa002d2..d4c988ef41ac5b731159da47570a38c846352623 100644 (file)
@@ -5,12 +5,12 @@
 
 import { CircularBuffer } from 'mnemonist'
 import assert from 'node:assert/strict'
-import { afterEach, describe, it } from 'node:test'
+import { afterEach, beforeEach, describe, it } from 'node:test'
 
 import type { ChargingStation } from '../../src/charging-station/index.js'
 import type { Statistics, TimestampedData } from '../../src/types/index.js'
 
-import { AvailabilityType, ChargingStationWorkerMessageEvents } from '../../src/types/index.js'
+import { ChargingStationWorkerMessageEvents } from '../../src/types/index.js'
 import {
   buildAddedMessage,
   buildDeletedMessage,
@@ -19,59 +19,36 @@ import {
   buildStoppedMessage,
   buildUpdatedMessage,
 } from '../../src/utils/MessageChannelUtils.js'
+import {
+  cleanupChargingStation,
+  createMockChargingStation,
+} from '../charging-station/ChargingStationTestUtils.js'
 import { standardCleanup } from '../helpers/TestLifecycleHelpers.js'
 
-/**
- * Creates a minimal mock station with properties needed by MessageChannelUtils builders.
- * @returns Mock charging station instance
- */
-function createMockStationForMessages (): ChargingStation {
-  return {
-    automaticTransactionGenerator: undefined,
-    bootNotificationResponse: {
-      currentTime: new Date('2024-01-01T00:00:00Z'),
-      interval: 300,
-      status: 'Accepted',
-    },
-    connectors: new Map([
-      [
-        0,
-        {
-          availability: AvailabilityType.Operative,
-          MeterValues: [],
-        },
-      ],
-      [
-        1,
-        {
-          availability: AvailabilityType.Operative,
-          MeterValues: [],
-        },
-      ],
-    ]),
-    evses: new Map(),
-    getAutomaticTransactionGeneratorConfiguration: () => undefined,
-    ocppConfiguration: { configurationKey: [] },
-    started: true,
-    stationInfo: {
+await describe('MessageChannelUtils', async () => {
+  let station: ChargingStation
+
+  beforeEach(() => {
+    const result = createMockChargingStation({
       baseName: 'CS-TEST',
-      chargingStationId: 'CS-TEST-00001',
-      hashId: 'test-hash',
-      templateIndex: 1,
-      templateName: 'test-template.json',
-    },
-    wsConnection: { readyState: 1 },
-    wsConnectionUrl: new URL('ws://localhost:8080/CS-TEST-00001'),
-  } as unknown as ChargingStation
-}
+      connectorsCount: 1,
+      started: true,
+      stationInfo: { hashId: 'test-hash' },
+    })
+    station = result.station
+    // Add wsConnectionUrl getter needed by MessageChannelUtils builders
+    Object.defineProperty(station, 'wsConnectionUrl', {
+      configurable: true,
+      get: () => new URL('ws://localhost:8080/CS-TEST-00001'),
+    })
+  })
 
-await describe('MessageChannelUtils', async () => {
   afterEach(() => {
+    cleanupChargingStation(station)
     standardCleanup()
   })
 
   await it('should build added message with correct event and data', () => {
-    const station = createMockStationForMessages()
     const message = buildAddedMessage(station)
 
     assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.added)
@@ -83,7 +60,6 @@ await describe('MessageChannelUtils', async () => {
   })
 
   await it('should build deleted message with correct event', () => {
-    const station = createMockStationForMessages()
     const message = buildDeletedMessage(station)
 
     assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.deleted)
@@ -92,7 +68,6 @@ await describe('MessageChannelUtils', async () => {
   })
 
   await it('should build started message with correct event', () => {
-    const station = createMockStationForMessages()
     const message = buildStartedMessage(station)
 
     assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.started)
@@ -100,7 +75,6 @@ await describe('MessageChannelUtils', async () => {
   })
 
   await it('should build stopped message with correct event', () => {
-    const station = createMockStationForMessages()
     const message = buildStoppedMessage(station)
 
     assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.stopped)
@@ -109,7 +83,6 @@ await describe('MessageChannelUtils', async () => {
   })
 
   await it('should build updated message with correct event', () => {
-    const station = createMockStationForMessages()
     const message = buildUpdatedMessage(station)
 
     assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.updated)
@@ -118,14 +91,12 @@ await describe('MessageChannelUtils', async () => {
   })
 
   await it('should include ws state in station messages', () => {
-    const station = createMockStationForMessages()
     const message = buildAddedMessage(station)
 
     assert.strictEqual(message.data.wsState, 1)
   })
 
   await it('should include connectors status in station messages', () => {
-    const station = createMockStationForMessages()
     const message = buildAddedMessage(station)
 
     assert.ok(Array.isArray(message.data.connectors))
index 181f52acd0559163d7a8ed3e2b5338f24202d6ce..4da1d553167e155de48494cda16dd1c8a74d8416 100644 (file)
             :key="entry.evseId != null ? `${entry.evseId}-${entry.connectorId}` : entry.connectorId"
             :atg-status="getATGStatus(entry.connectorId)"
             :charging-station-id="chargingStation.stationInfo.chargingStationId"
-            :connector="entry.connector"
+            :connector="entry.connectorStatus"
             :connector-id="entry.connectorId"
             :evse-id="entry.evseId"
             :hash-id="chargingStation.stationInfo.hashId"
 import { computed } from 'vue'
 import { useToast } from 'vue-toast-notification'
 
-import type { ChargingStationData, ConnectorStatus, Status } from '@/types'
+import type { ChargingStationData, ConnectorEntry, Status } from '@/types'
 
 import Button from '@/components/buttons/Button.vue'
 import StateButton from '@/components/buttons/StateButton.vue'
@@ -150,12 +150,6 @@ import {
   useUIClient,
 } from '@/composables'
 
-interface ConnectorTableEntry {
-  connector: ConnectorStatus
-  connectorId: number
-  evseId?: number
-}
-
 const props = defineProps<{
   chargingStation: ChargingStationData
 }>()
@@ -164,16 +158,16 @@ const $emit = defineEmits(['need-refresh'])
 
 const isWebSocketOpen = computed(() => props.chargingStation.wsState === WebSocket.OPEN)
 
-const getConnectorEntries = (): ConnectorTableEntry[] => {
+const getConnectorEntries = (): ConnectorEntry[] => {
   if (Array.isArray(props.chargingStation.evses) && props.chargingStation.evses.length > 0) {
-    const entries: ConnectorTableEntry[] = []
+    const entries: ConnectorEntry[] = []
     for (const evse of props.chargingStation.evses) {
       if (evse.evseId > 0) {
-        for (const entry of evse.connectors) {
+        for (const entry of evse.evseStatus.connectors) {
           if (entry.connectorId > 0) {
             entries.push({
-              connector: entry.connector,
               connectorId: entry.connectorId,
+              connectorStatus: entry.connectorStatus,
               evseId: evse.evseId,
             })
           }
@@ -185,8 +179,8 @@ const getConnectorEntries = (): ConnectorTableEntry[] => {
   return (props.chargingStation.connectors ?? [])
     .filter(c => c.connectorId > 0)
     .map(entry => ({
-      connector: entry.connector,
       connectorId: entry.connectorId,
+      connectorStatus: entry.connectorStatus,
     }))
 }
 const getATGStatus = (connectorId: number): Status | undefined => {
index 71d230b98efa48d6771f3944cd9c4bdb7fe2b559..5c00b160657417d11b9d90853b2e2f0b4eedbee8 100644 (file)
@@ -249,8 +249,9 @@ export interface ConfigurationKey extends OCPPConfigurationKey {
 }
 
 export interface ConnectorEntry extends JsonObject {
-  connector: ConnectorStatus
   connectorId: number
+  connectorStatus: ConnectorStatus
+  evseId?: number
 }
 
 export interface ConnectorStatus extends JsonObject {
@@ -276,9 +277,11 @@ export interface ConnectorStatus extends JsonObject {
 }
 
 export interface EvseEntry extends JsonObject {
-  availability: AvailabilityType
-  connectors: ConnectorEntry[]
   evseId: number
+  evseStatus: {
+    availability: AvailabilityType
+    connectors: ConnectorEntry[]
+  }
 }
 
 export interface OCPP20EVSEType extends JsonObject {
index f59974a1a493c9a729e7350a28a3ffd29d155afa..303c1cd1ece078c35bd4cc12da35a1631517459a 100644 (file)
@@ -242,9 +242,9 @@ describe('CSData', () => {
     it('should generate entries from connectors array for OCPP 1.6', () => {
       const station = createChargingStationData({
         connectors: [
-          { connector: createConnectorStatus(), connectorId: 0 },
-          { connector: createConnectorStatus(), connectorId: 1 },
-          { connector: createConnectorStatus(), connectorId: 2 },
+          { connectorId: 0, connectorStatus: createConnectorStatus() },
+          { connectorId: 1, connectorStatus: createConnectorStatus() },
+          { connectorId: 2, connectorStatus: createConnectorStatus() },
         ],
       })
       const wrapper = mountCSData(station)
@@ -253,7 +253,7 @@ describe('CSData', () => {
 
     it('should filter out connector 0', () => {
       const station = createChargingStationData({
-        connectors: [{ connector: createConnectorStatus(), connectorId: 0 }],
+        connectors: [{ connectorId: 0, connectorStatus: createConnectorStatus() }],
       })
       const wrapper = mountCSData(station)
       expect(wrapper.findAllComponents(CSConnector)).toHaveLength(0)
@@ -264,16 +264,25 @@ describe('CSData', () => {
         connectors: [],
         evses: [
           createEvseEntry({
-            connectors: [{ connector: createConnectorStatus(), connectorId: 0 }],
             evseId: 0,
+            evseStatus: {
+              availability: 'Operative' as never,
+              connectors: [{ connectorId: 0, connectorStatus: createConnectorStatus() }],
+            },
           }),
           createEvseEntry({
-            connectors: [{ connector: createConnectorStatus(), connectorId: 1 }],
             evseId: 1,
+            evseStatus: {
+              availability: 'Operative' as never,
+              connectors: [{ connectorId: 1, connectorStatus: createConnectorStatus() }],
+            },
           }),
           createEvseEntry({
-            connectors: [{ connector: createConnectorStatus(), connectorId: 1 }],
             evseId: 2,
+            evseStatus: {
+              availability: 'Operative' as never,
+              connectors: [{ connectorId: 1, connectorStatus: createConnectorStatus() }],
+            },
           }),
         ],
         stationInfo: createStationInfo({ ocppVersion: OCPPVersion.VERSION_201 }),
@@ -287,8 +296,11 @@ describe('CSData', () => {
         connectors: [],
         evses: [
           createEvseEntry({
-            connectors: [{ connector: createConnectorStatus(), connectorId: 0 }],
             evseId: 0,
+            evseStatus: {
+              availability: 'Operative' as never,
+              connectors: [{ connectorId: 0, connectorStatus: createConnectorStatus() }],
+            },
           }),
         ],
         stationInfo: createStationInfo({ ocppVersion: OCPPVersion.VERSION_201 }),
index 3a57a109afe13bc145fd2b3dd49bbbee543572e8..bb77111df46235435b612a193f04e983970f3a55 100644 (file)
@@ -40,7 +40,7 @@ export function createChargingStationData (
       interval: 60,
       status: OCPP16RegistrationStatus.ACCEPTED,
     },
-    connectors: [{ connector: createConnectorStatus(), connectorId: 1 }],
+    connectors: [{ connectorId: 1, connectorStatus: createConnectorStatus() }],
     ocppConfiguration: { configurationKey: [] },
     started: true,
     stationInfo: createStationInfo(),
@@ -70,9 +70,11 @@ export function createConnectorStatus (overrides?: Partial<ConnectorStatus>): Co
  */
 export function createEvseEntry (overrides?: Partial<EvseEntry>): EvseEntry {
   return {
-    availability: OCPP16AvailabilityType.OPERATIVE,
-    connectors: [{ connector: createConnectorStatus(), connectorId: 1 }],
     evseId: 1,
+    evseStatus: {
+      availability: OCPP16AvailabilityType.OPERATIVE,
+      connectors: [{ connectorId: 1, connectorStatus: createConnectorStatus() }],
+    },
     ...overrides,
   }
 }