}
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))
}
}
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)
}
}
}
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)
}
}
type ChargingStationOcppConfiguration,
type ChargingStationOptions,
type ChargingStationTemplate,
+ type ConnectorEntry,
type ConnectorStatus,
ConnectorStatusEnum,
CurrentType,
type ErrorCallback,
type ErrorResponse,
ErrorType,
+ type EvseEntry,
type EvseStatus,
type EvseStatusConfiguration,
FileType,
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
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
): 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
}
/**
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
}
/**
* @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
+ )
}
/**
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 {
* @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 {
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))
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`)
}
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(
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<
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
}
}
* @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
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(
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`
)
}
)
.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,
chargingStation,
OCPP16RequestCommand.STATUS_NOTIFICATION,
{
- connectorId: id,
+ connectorId,
status: connectorStatus.status as OCPP16ChargePointStatus,
} as unknown as OCPP16StatusNotificationRequest,
{
? 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() ||
}
} 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) {
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<
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
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) {
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) {
CertificateSigningUseEnumType,
ChangeAvailabilityStatusEnumType,
ConnectorEnumType,
+ type ConnectorStatus,
ConnectorStatusEnum,
CustomerInformationStatusEnumType,
DataEnumType,
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:`,
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
}
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:
})
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
}
}
+ 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
operationalStatus: OCPP20OperationalStatusEnumType,
newConnectorStatus: OCPP20ConnectorStatusEnumType
): OCPP20ChangeAvailabilityResponse {
- if (!chargingStation.evses.has(evseId)) {
+ if (!chargingStation.hasEvse(evseId)) {
return {
status: ChangeAvailabilityStatusEnumType.Rejected,
statusInfo: {
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(
}
}
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)
}
}
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`
)
}
// 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)
// 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,
}
}
}
}
- 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`
}
}
- if (!chargingStation.evses.has(evseId)) {
+ if (!chargingStation.hasEvse(evseId)) {
return {
status: UnlockStatusEnumType.UnknownConnector,
statusInfo: {
}
}
- 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`
)
}
+ 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,
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) {
}
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)
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
+ )
+ })
}
}
}
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
+ )
+ })
}
}
// 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, [
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
)
}
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)
}
}
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
},
}
}
- if (!chargingStation.evses.has(evse.id)) {
+ if (!chargingStation.hasEvse(evse.id)) {
return {
status: TriggerMessageStatusEnumType.Rejected,
statusInfo: {
}
}
} 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
+ )
+ })
+ )
}
}
}
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
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
} 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'
export type ChargingStationWorkerMessageEvents =
| ChargingStationEvents
| ChargingStationMessageEvents
-
-export interface ConnectorEntry {
- connector: ConnectorStatus
- connectorId: number
-}
-
-export interface EvseEntry {
- availability: AvailabilityType
- connectors: ConnectorEntry[]
- evseId: number
-}
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
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>
type ChargingStationWorkerMessage,
type ChargingStationWorkerMessageData,
ChargingStationWorkerMessageEvents,
- type ConnectorEntry,
- type EvseEntry,
} from './ChargingStationWorker.js'
export {
ApplicationProtocolVersion,
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'
}
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()
}
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)
})
// 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 () => {
// 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)
})
})
})
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', () => {
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
})
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)
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)
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)
}
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)
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)
ChargingStationInfo,
ChargingStationOcppConfiguration,
ChargingStationTemplate,
+ ConnectorEntry,
ConnectorStatus,
+ EvseEntry,
EvseStatus,
Reservation,
StopTransactionReason,
}
// 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
}
// 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)
* @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",{}]')
* ```
return connectors.has(connectorId)
},
+ hasEvse (evseId: number): boolean {
+ return evses.has(evseId)
+ },
+
// Getters
get hasEvses (): boolean {
return useEvses
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 {
}
// 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)
'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 = []
)
// 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' }]
}
}
})
// 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' }]
}
}
})
* @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
})
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`
+ )
}
})
})
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)
}
})
})
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
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)
})
})
OCPP20ChargingRateUnitEnumType,
OCPP20RequestStartTransactionRequest,
OCPP20RequestStartTransactionResponse,
+ OCPP20TransactionEventOptions,
OCPP20TransactionEventRequest,
} from '../../../../src/types/index.js'
]
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 () => {
}
// 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)
}
// 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)
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)
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'
})
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'
})
// 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) {
*/
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
}
}
} 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
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 {
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()
})
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: [],
transactionUpdatedMeterValuesSetInterval: interval1 as unknown as NodeJS.Timeout,
} as unknown as ConnectorStatus)
- const station = createMockStationForConfigUtils({ connectors })
const result = buildConnectorsStatus(station)
assert.strictEqual(result.length, 2)
})
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: [],
transactionUpdatedMeterValuesSetInterval: undefined,
} as unknown as ConnectorStatus)
- const station = createMockStationForConfigUtils({ connectors })
const result = buildConnectorsStatus(station)
assert.strictEqual(result.length, 1)
})
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)
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,
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)
})
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,
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)
})
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,
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][]
})
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)
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)
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)
})
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)
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)
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)
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)
})
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() }],
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,
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,
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)
})
})
})
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,
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)
})
await it('should build deleted message with correct event', () => {
- const station = createMockStationForMessages()
const message = buildDeletedMessage(station)
assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.deleted)
})
await it('should build started message with correct event', () => {
- const station = createMockStationForMessages()
const message = buildStartedMessage(station)
assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.started)
})
await it('should build stopped message with correct event', () => {
- const station = createMockStationForMessages()
const message = buildStoppedMessage(station)
assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.stopped)
})
await it('should build updated message with correct event', () => {
- const station = createMockStationForMessages()
const message = buildUpdatedMessage(station)
assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.updated)
})
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))
: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'
useUIClient,
} from '@/composables'
-interface ConnectorTableEntry {
- connector: ConnectorStatus
- connectorId: number
- evseId?: number
-}
-
const props = defineProps<{
chargingStation: ChargingStationData
}>()
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,
})
}
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 => {
}
export interface ConnectorEntry extends JsonObject {
- connector: ConnectorStatus
connectorId: number
+ connectorStatus: ConnectorStatus
+ evseId?: number
}
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 {
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)
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)
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 }),
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 }),
interval: 60,
status: OCPP16RegistrationStatus.ACCEPTED,
},
- connectors: [{ connector: createConnectorStatus(), connectorId: 1 }],
+ connectors: [{ connectorId: 1, connectorStatus: createConnectorStatus() }],
ocppConfiguration: { configurationKey: [] },
started: true,
stationInfo: createStationInfo(),
*/
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,
}
}