-/* eslint-disable @typescript-eslint/unified-signatures */
-
import { secondsToMilliseconds } from 'date-fns'
import { type ChargingStation, resetConnectorStatus } from '../../../charging-station/index.js'
import {
ConnectorStatusEnum,
ErrorType,
+ type JsonObject,
OCPP20ComponentName,
OCPP20IncomingRequestCommand,
OCPP20RequestCommand,
import {
type OCPP20EVSEType,
OCPP20ReasonEnumType,
- type OCPP20TransactionContext,
type OCPP20TransactionEventOptions,
type OCPP20TransactionType,
} from '../../../types/ocpp/2.0/Transaction.js'
import {
Constants,
convertToIntOrNaN,
+ generateUUID,
logger,
validateIdentifierString,
} from '../../../utils/index.js'
import { getConfigurationKey } from '../../ConfigurationKeyUtils.js'
import { OCPPServiceUtils, sendAndSetConnectorStatus } from '../OCPPServiceUtils.js'
-import { OCPP20Constants } from './OCPP20Constants.js'
import { OCPP20VariableManager } from './OCPP20VariableManager.js'
const moduleName = 'OCPP20ServiceUtils'
[OCPP20RequestCommand.TRANSACTION_EVENT, 'TransactionEvent'],
]
- /**
- * Build a TransactionEvent request according to OCPP 2.0.1 specification
- *
- * This method creates a properly formatted TransactionEventRequest that complies with
- * OCPP 2.0.1 requirements including F01, E01, E06, and TriggerReason specifications.
- *
- * Key features:
- * - Automatic per-EVSE sequence number management
- * - Full TriggerReason validation (21 enum values)
- * - EVSE/connector mapping and validation
- * - Transaction UUID handling
- * - Comprehensive parameter validation
- * @param chargingStation - The charging station instance
- * @param eventType - Transaction event type (Started, Updated, Ended)
- * @param triggerReason - Reason that triggered the event (21 OCPP 2.0.1 values)
- * @param connectorId - Connector identifier
- * @param transactionId - Transaction UUID (required for all events)
- * @param options - Optional parameters for the transaction event
- * @param options.evseId
- * @param options.idToken
- * @param options.meterValue
- * @param options.chargingState
- * @param options.stoppedReason
- * @param options.remoteStartId
- * @param options.cableMaxCurrent
- * @param options.numberOfPhasesUsed
- * @param options.offline
- * @param options.reservationId
- * @param options.customData
- * @returns Promise<OCPP20TransactionEventRequest> - Built transaction event request
- * @throws {OCPPError} When parameters are invalid or EVSE mapping fails
- */
- public static buildTransactionEvent (
- chargingStation: ChargingStation,
- eventType: OCPP20TransactionEventEnumType,
- context: OCPP20TransactionContext,
- connectorId: number,
- transactionId: string,
- options?: OCPP20TransactionEventOptions
- ): OCPP20TransactionEventRequest
- public static buildTransactionEvent (
- chargingStation: ChargingStation,
- eventType: OCPP20TransactionEventEnumType,
- triggerReason: OCPP20TriggerReasonEnumType,
- connectorId: number,
- transactionId: string,
- options?: OCPP20TransactionEventOptions
- ): OCPP20TransactionEventRequest
- public static buildTransactionEvent (
- chargingStation: ChargingStation,
- eventType: OCPP20TransactionEventEnumType,
- triggerReasonOrContext: OCPP20TransactionContext | OCPP20TriggerReasonEnumType,
- connectorId: number,
- transactionId: string,
- options: OCPP20TransactionEventOptions = {}
- ): OCPP20TransactionEventRequest {
- const isContext = typeof triggerReasonOrContext === 'object'
- const triggerReason = isContext
- ? this.selectTriggerReason(eventType, triggerReasonOrContext)
- : triggerReasonOrContext
-
- // Validate transaction ID format (must be non-empty string ≤36 characters per OCPP 2.0.1)
- if (!validateIdentifierString(transactionId, 36)) {
- const errorMsg = `Invalid transaction ID format (must be non-empty string ≤36 characters): ${transactionId}`
- logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`
- )
- throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
- }
-
- const evseId = options.evseId ?? chargingStation.getEvseIdByConnectorId(connectorId)
- if (evseId == null) {
- const errorMsg = `Cannot find EVSE ID for connector ${connectorId.toString()}`
- logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`
- )
- throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
- }
-
- const connectorStatus = chargingStation.getConnectorStatus(connectorId)
- if (connectorStatus == null) {
- const errorMsg = `Cannot find connector status for connector ${connectorId.toString()}`
- logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`
- )
- throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
- }
-
- // Per-EVSE sequence number management (OCPP 2.0.1 §1.3.2.1)
- if (connectorStatus.transactionSeqNo == null) {
- connectorStatus.transactionSeqNo = 0
- } else {
- connectorStatus.transactionSeqNo = connectorStatus.transactionSeqNo + 1
- }
-
- // Build EVSE object (E01.FR.16: only include in first TransactionEvent after EV connected)
- let evse: OCPP20EVSEType | undefined
- if (connectorStatus.transactionEvseSent !== true) {
- evse = { id: evseId }
- if (connectorId !== evseId) {
- evse.connectorId = connectorId
- }
- connectorStatus.transactionEvseSent = true
- }
-
- const transactionInfo: OCPP20TransactionType = {
- transactionId: transactionId as UUIDv4,
- }
-
- if (options.chargingState !== undefined) {
- transactionInfo.chargingState = options.chargingState
- }
- if (options.stoppedReason !== undefined) {
- transactionInfo.stoppedReason = options.stoppedReason
- }
- if (options.remoteStartId !== undefined) {
- transactionInfo.remoteStartId = options.remoteStartId
- }
-
- const transactionEventRequest: OCPP20TransactionEventRequest = {
- eventType,
- seqNo: connectorStatus.transactionSeqNo,
- timestamp: new Date(),
- transactionInfo,
- triggerReason,
- }
-
- // E01.FR.16: Include evse only in first TransactionEvent
- if (evse !== undefined) {
- transactionEventRequest.evse = evse
- }
-
- // E03.FR.01: Include idToken only once per transaction (first event after authorization)
- if (options.idToken !== undefined && connectorStatus.transactionIdTokenSent !== true) {
- transactionEventRequest.idToken = options.idToken
- connectorStatus.transactionIdTokenSent = true
- }
- if (options.meterValue !== undefined && options.meterValue.length > 0) {
- transactionEventRequest.meterValue = options.meterValue
- }
- if (options.cableMaxCurrent !== undefined) {
- transactionEventRequest.cableMaxCurrent = options.cableMaxCurrent
- }
- if (options.numberOfPhasesUsed !== undefined) {
- transactionEventRequest.numberOfPhasesUsed = options.numberOfPhasesUsed
- }
- if (options.offline !== undefined) {
- transactionEventRequest.offline = options.offline
- }
- if (options.reservationId !== undefined) {
- transactionEventRequest.reservationId = options.reservationId
- }
- if (options.customData !== undefined) {
- transactionEventRequest.customData = options.customData
- }
-
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: Building TransactionEvent for trigger ${triggerReason}`
- )
-
- return transactionEventRequest
- }
-
/**
* OCPP 2.0 Incoming Request Service validator configurations
* @returns Array of validator configuration tuples
}
}
- /**
- * Intelligently select appropriate TriggerReason based on transaction context
- *
- * This method implements the E02.FR.17 requirement for context-aware TriggerReason selection.
- * It analyzes the transaction context to determine the most appropriate TriggerReason according
- * to OCPP 2.0.1 specification and best practices.
- *
- * Selection Logic (by priority):
- * 1. Remote commands (RequestStartTransaction, RequestStopTransaction, etc.) -> RemoteStart/RemoteStop
- * 2. Authorization events (token presented) -> Authorized/StopAuthorized/Deauthorized
- * 3. Cable physical actions -> CablePluggedIn
- * 4. Charging state transitions -> ChargingStateChanged
- * 5. System events (EV detection, communication) -> EVDetected/EVDeparted/EVCommunicationLost
- * 6. Meter value events -> MeterValuePeriodic/MeterValueClock
- * 7. Energy/Time limits -> EnergyLimitReached/TimeLimitReached
- * 8. Abnormal conditions -> AbnormalCondition
- * @param eventType - The transaction event type (Started, Updated, Ended)
- * @param context - Context information describing the trigger source and details
- * @returns OCPP20TriggerReasonEnumType - The most appropriate trigger reason
- */
- public static selectTriggerReason (
- eventType: OCPP20TransactionEventEnumType,
- context: OCPP20TransactionContext
- ): OCPP20TriggerReasonEnumType {
- const candidates = OCPP20Constants.TriggerReasonMapping.filter(
- entry => entry.source === context.source
- )
-
- for (const entry of candidates) {
- if (context.source === 'remote_command' && context.command != null) {
- if (
- (context.command === OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.RemoteStart) ||
- (context.command === OCPP20IncomingRequestCommand.REQUEST_STOP_TRANSACTION &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.RemoteStop) ||
- (context.command === OCPP20IncomingRequestCommand.RESET &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.ResetCommand) ||
- (context.command === OCPP20IncomingRequestCommand.TRIGGER_MESSAGE &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.Trigger) ||
- (context.command === OCPP20IncomingRequestCommand.UNLOCK_CONNECTOR &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.UnlockCommand)
- ) {
- return entry.triggerReason
- }
- }
-
- if (context.source === 'local_authorization' && context.authorizationMethod != null) {
- if (context.isDeauthorized === true) {
- if (entry.triggerReason === OCPP20TriggerReasonEnumType.Deauthorized) {
- return entry.triggerReason
- }
- } else if (
- (context.authorizationMethod === 'groupIdToken' ||
- context.authorizationMethod === 'idToken') &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.Authorized
- ) {
- return entry.triggerReason
- } else if (
- context.authorizationMethod === 'stopAuthorized' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.StopAuthorized
- ) {
- return entry.triggerReason
- }
- }
-
- if (context.source === 'cable_action' && context.cableState != null) {
- if (
- (context.cableState === 'detected' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.EVDetected) ||
- (context.cableState === 'plugged_in' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.CablePluggedIn) ||
- (context.cableState === 'unplugged' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.EVDeparted)
- ) {
- return entry.triggerReason
- }
- }
-
- if (
- context.source === 'charging_state' &&
- context.chargingStateChange != null &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.ChargingStateChanged
- ) {
- return entry.triggerReason
- }
-
- if (context.source === 'system_event' && context.systemEvent != null) {
- if (
- (context.systemEvent === 'ev_communication_lost' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.EVCommunicationLost) ||
- (context.systemEvent === 'ev_connect_timeout' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.EVConnectTimeout) ||
- (context.systemEvent === 'ev_departed' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.EVDeparted) ||
- (context.systemEvent === 'ev_detected' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.EVDetected)
- ) {
- return entry.triggerReason
- }
- }
-
- if (context.source === 'meter_value') {
- if (
- (context.isSignedDataReceived === true &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.SignedDataReceived) ||
- (context.isPeriodicMeterValue === true &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.MeterValuePeriodic) ||
- (context.isSignedDataReceived !== true &&
- context.isPeriodicMeterValue !== true &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.MeterValueClock)
- ) {
- return entry.triggerReason
- }
- }
-
- if (
- (context.source === 'energy_limit' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.EnergyLimitReached) ||
- (context.source === 'time_limit' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.TimeLimitReached) ||
- (context.source === 'external_limit' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.ChargingRateChanged)
- ) {
- return entry.triggerReason
- }
-
- if (
- context.source === 'abnormal_condition' &&
- entry.triggerReason === OCPP20TriggerReasonEnumType.AbnormalCondition
- ) {
- return entry.triggerReason
- }
- }
-
- logger.warn(
- `${moduleName}.selectTriggerReason: No matching context found for source '${context.source}', defaulting to Trigger`
- )
- return OCPP20TriggerReasonEnumType.Trigger
- }
-
public static async sendQueuedTransactionEvents (
chargingStation: ChargingStation,
connectorId: number
}
}
- public static async sendTransactionEvent (
- chargingStation: ChargingStation,
- eventType: OCPP20TransactionEventEnumType,
- context: OCPP20TransactionContext,
- connectorId: number,
- transactionId: string,
- options?: OCPP20TransactionEventOptions
- ): Promise<OCPP20TransactionEventResponse>
public static async sendTransactionEvent (
chargingStation: ChargingStation,
eventType: OCPP20TransactionEventEnumType,
triggerReason: OCPP20TriggerReasonEnumType,
connectorId: number,
transactionId: string,
- options?: OCPP20TransactionEventOptions
- ): Promise<OCPP20TransactionEventResponse>
- // Implementation with union type + type guard
- public static async sendTransactionEvent (
- chargingStation: ChargingStation,
- eventType: OCPP20TransactionEventEnumType,
- triggerReasonOrContext: OCPP20TransactionContext | OCPP20TriggerReasonEnumType,
- connectorId: number,
- transactionId: string,
options: OCPP20TransactionEventOptions = {}
): Promise<OCPP20TransactionEventResponse> {
try {
- // Type guard: distinguish between context object and direct trigger reason
- const isContext = typeof triggerReasonOrContext === 'object'
- const triggerReason = isContext
- ? this.selectTriggerReason(eventType, triggerReasonOrContext)
- : triggerReasonOrContext
-
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
if (connectorStatus == null) {
const errorMsg = `Cannot find connector status for connector ${connectorId.toString()}`
// Offline: build and queue pre-built payload (sent as-is via rawPayload on reconnect)
if (!chargingStation.isWebSocketConnectionOpened()) {
- const transactionEventRequest = OCPP20ServiceUtils.buildTransactionEvent(
+ const transactionEventRequest = buildTransactionEvent(
chargingStation,
eventType,
triggerReason,
}
}
}
+export function buildTransactionEvent (
+ chargingStation: ChargingStation,
+ eventType: OCPP20TransactionEventEnumType,
+ triggerReason: OCPP20TriggerReasonEnumType,
+ connectorId: number,
+ transactionId: string,
+ options?: OCPP20TransactionEventOptions
+): OCPP20TransactionEventRequest
+export function buildTransactionEvent (
+ chargingStation: ChargingStation,
+ commandParams: JsonObject
+): OCPP20TransactionEventRequest
+/**
+ *
+ * @param chargingStation
+ * @param eventTypeOrParams
+ * @param triggerReasonArg
+ * @param connectorIdArg
+ * @param transactionIdArg
+ * @param options
+ */
+export function buildTransactionEvent (
+ chargingStation: ChargingStation,
+ eventTypeOrParams: JsonObject | OCPP20TransactionEventEnumType,
+ triggerReasonArg?: OCPP20TriggerReasonEnumType,
+ connectorIdArg?: number,
+ transactionIdArg?: string,
+ options: OCPP20TransactionEventOptions = {}
+): OCPP20TransactionEventRequest {
+ let eventType: OCPP20TransactionEventEnumType
+ let triggerReason: OCPP20TriggerReasonEnumType
+ let connectorId: number
+ let transactionId: string
+
+ if (typeof eventTypeOrParams === 'object') {
+ const params = eventTypeOrParams
+ eventType = params.eventType as OCPP20TransactionEventEnumType
+ triggerReason =
+ params.triggerReason != null
+ ? (params.triggerReason as OCPP20TriggerReasonEnumType)
+ : eventType === OCPP20TransactionEventEnumType.Ended
+ ? OCPP20TriggerReasonEnumType.RemoteStop
+ : OCPP20TriggerReasonEnumType.Authorized
+ const evse = params.evse as undefined | { connectorId?: number; id?: number }
+ connectorId =
+ params.connectorId != null
+ ? (params.connectorId as number)
+ : (evse?.connectorId ?? evse?.id ?? 1)
+ transactionId =
+ params.transactionId != null
+ ? (params.transactionId as string)
+ : eventType === OCPP20TransactionEventEnumType.Ended
+ ? (chargingStation.getConnectorStatus(connectorId)?.transactionId?.toString() ??
+ generateUUID())
+ : generateUUID()
+ options = params as unknown as OCPP20TransactionEventOptions
+ } else {
+ eventType = eventTypeOrParams
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ triggerReason = triggerReasonArg!
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ connectorId = connectorIdArg!
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ transactionId = transactionIdArg!
+ }
+
+ if (!validateIdentifierString(transactionId, 36)) {
+ const errorMsg = `Invalid transaction ID format (must be non-empty string ≤36 characters): ${transactionId}`
+ logger.error(`${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`)
+ throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
+ }
+
+ const evseId = options.evseId ?? chargingStation.getEvseIdByConnectorId(connectorId)
+ if (evseId == null) {
+ const errorMsg = `Cannot find EVSE ID for connector ${connectorId.toString()}`
+ logger.error(`${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`)
+ throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
+ }
+
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ if (connectorStatus == null) {
+ const errorMsg = `Cannot find connector status for connector ${connectorId.toString()}`
+ logger.error(`${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`)
+ throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
+ }
+
+ if (connectorStatus.transactionSeqNo == null) {
+ connectorStatus.transactionSeqNo = 0
+ } else {
+ connectorStatus.transactionSeqNo = connectorStatus.transactionSeqNo + 1
+ }
+
+ // E01.FR.16: only include EVSE in first TransactionEvent
+ let evse: OCPP20EVSEType | undefined
+ if (connectorStatus.transactionEvseSent !== true) {
+ evse = { id: evseId }
+ if (connectorId !== evseId) {
+ evse.connectorId = connectorId
+ }
+ connectorStatus.transactionEvseSent = true
+ }
+
+ const transactionInfo: OCPP20TransactionType = {
+ transactionId: transactionId as UUIDv4,
+ }
+
+ if (options.chargingState !== undefined) {
+ transactionInfo.chargingState = options.chargingState
+ }
+ if (options.stoppedReason !== undefined) {
+ transactionInfo.stoppedReason = options.stoppedReason
+ }
+ if (options.remoteStartId !== undefined) {
+ transactionInfo.remoteStartId = options.remoteStartId
+ }
+
+ const transactionEventRequest: OCPP20TransactionEventRequest = {
+ eventType,
+ seqNo: connectorStatus.transactionSeqNo,
+ timestamp: new Date(),
+ transactionInfo,
+ triggerReason,
+ }
+
+ if (evse !== undefined) {
+ transactionEventRequest.evse = evse
+ }
+
+ // E03.FR.01: Include idToken only once per transaction
+ if (options.idToken !== undefined && connectorStatus.transactionIdTokenSent !== true) {
+ transactionEventRequest.idToken = options.idToken
+ connectorStatus.transactionIdTokenSent = true
+ }
+ if (options.meterValue !== undefined && options.meterValue.length > 0) {
+ transactionEventRequest.meterValue = options.meterValue
+ }
+ if (options.cableMaxCurrent !== undefined) {
+ transactionEventRequest.cableMaxCurrent = options.cableMaxCurrent
+ }
+ if (options.numberOfPhasesUsed !== undefined) {
+ transactionEventRequest.numberOfPhasesUsed = options.numberOfPhasesUsed
+ }
+ if (options.offline !== undefined) {
+ transactionEventRequest.offline = options.offline
+ }
+ if (options.reservationId !== undefined) {
+ transactionEventRequest.reservationId = options.reservationId
+ }
+ if (options.customData !== undefined) {
+ transactionEventRequest.customData = options.customData
+ }
+
+ logger.debug(
+ `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: Building TransactionEvent for trigger ${triggerReason}`
+ )
+
+ return transactionEventRequest
+}
import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
import type { EmptyObject } from '../../../../src/types/index.js'
-import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
+import {
+ buildTransactionEvent,
+ OCPP20ServiceUtils,
+} from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
import {
ConnectorStatusEnum,
OCPP20TransactionEventEnumType,
OCPP20TriggerReasonEnumType,
OCPPVersion,
} from '../../../../src/types/index.js'
-import { OCPP20IncomingRequestCommand } from '../../../../src/types/ocpp/2.0/Requests.js'
import {
OCPP20ChargingStateEnumType,
OCPP20IdTokenEnumType,
type OCPP20IdTokenType,
OCPP20ReasonEnumType,
- type OCPP20TransactionContext,
type OCPP20TransactionType,
} from '../../../../src/types/ocpp/2.0/Transaction.js'
import { Constants, generateUUID } from '../../../../src/utils/index.js'
type MockStationWithTracking,
resetConnectorTransactionState,
resetLimits,
- TransactionContextFixtures,
} from './OCPP20TestUtils.js'
// ============================================================================
// Transaction Flow Patterns for Parameterized Testing
id: 'cableFirst',
includeIdToken: false,
name: 'E02 - Cable-First',
- startContext: TransactionContextFixtures.cablePluggedIn(),
},
{
description: 'E03 IdToken-First',
id: 'idTokenFirst',
includeIdToken: true,
name: 'E03 - IdToken-First',
- startContext: TransactionContextFixtures.idTokenAuthorized(),
},
{
description: 'Remote Start',
id: 'remoteStart',
includeIdToken: false,
name: 'Remote Start',
- startContext: TransactionContextFixtures.remoteStart(),
},
] as const
// Reset sequence number to simulate new transaction
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
- const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const transactionEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
triggerReason,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// Build first event (Started)
- const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const startEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
)
// Build second event (Updated)
- const updateEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const updateEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.MeterValuePeriodic,
)
// Build third event (Ended)
- const endEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const endEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Ended,
OCPP20TriggerReasonEnumType.StopAuthorized,
reservationId: 67890,
}
- const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const transactionEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
'this-string-is-way-too-long-for-a-valid-transaction-id-exceeds-36-chars'
try {
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
for (const triggerReason of triggerReasons) {
- const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const transactionEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
triggerReason,
const connectorId = 1
// First, build a transaction event to set sequence number
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
- const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const transactionEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
- const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const transactionEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
})
})
- // FR: E01.FR.04 - TriggerReason selection based on transaction context
- await describe('Context-Aware TriggerReason Selection', async () => {
- await describe('selectTriggerReason', async () => {
- await it('should select RemoteStart for remote_command context with RequestStartTransaction', () => {
- const context: OCPP20TransactionContext = {
- command: OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION,
- source: 'remote_command',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStart)
- })
-
- await it('should select RemoteStop for remote_command context with RequestStopTransaction', () => {
- const context: OCPP20TransactionContext = {
- command: OCPP20IncomingRequestCommand.REQUEST_STOP_TRANSACTION,
- source: 'remote_command',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStop)
- })
-
- await it('should select UnlockCommand for remote_command context with UnlockConnector', () => {
- const context: OCPP20TransactionContext = {
- command: OCPP20IncomingRequestCommand.UNLOCK_CONNECTOR,
- source: 'remote_command',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Updated,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.UnlockCommand)
- })
-
- await it('should select ResetCommand for remote_command context with Reset', () => {
- const context: OCPP20TransactionContext = {
- command: OCPP20IncomingRequestCommand.RESET,
- source: 'remote_command',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.ResetCommand)
- })
-
- await it('should select Trigger for remote_command context with TriggerMessage', () => {
- const context: OCPP20TransactionContext = {
- command: OCPP20IncomingRequestCommand.TRIGGER_MESSAGE,
- source: 'remote_command',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Updated,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
- })
-
- await it('should select Authorized for local_authorization context with idToken', () => {
- const context: OCPP20TransactionContext = {
- authorizationMethod: 'idToken',
- source: 'local_authorization',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Authorized)
- })
-
- await it('should select StopAuthorized for local_authorization context with stopAuthorized', () => {
- const context: OCPP20TransactionContext = {
- authorizationMethod: 'stopAuthorized',
- source: 'local_authorization',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.StopAuthorized)
- })
-
- await it('should select Deauthorized when isDeauthorized flag is true', () => {
- const context: OCPP20TransactionContext = {
- authorizationMethod: 'idToken',
- isDeauthorized: true,
- source: 'local_authorization',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
- })
-
- await it('should select ChargingStateChanged for charging_state context', () => {
- const context: OCPP20TransactionContext = {
- chargingStateChange: {
- from: OCPP20ChargingStateEnumType.Idle,
- to: OCPP20ChargingStateEnumType.Charging,
- },
- source: 'charging_state',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Updated,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.ChargingStateChanged)
- })
-
- await it('should select MeterValuePeriodic for meter_value context with periodic flag', () => {
- const context: OCPP20TransactionContext = {
- isPeriodicMeterValue: true,
- source: 'meter_value',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Updated,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.MeterValuePeriodic)
- })
-
- await it('should select MeterValueClock for meter_value context without periodic flag', () => {
- const context: OCPP20TransactionContext = {
- isPeriodicMeterValue: false,
- source: 'meter_value',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Updated,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.MeterValueClock)
- })
-
- await it('should select SignedDataReceived when isSignedDataReceived flag is true', () => {
- const context: OCPP20TransactionContext = {
- isSignedDataReceived: true,
- source: 'meter_value',
- }
+ await describe('sendTransactionEvent with context parameter', async () => {
+ await it('should send TransactionEvent with context-aware TriggerReason selection', async () => {
+ const connectorId = 1
+ const transactionId = generateUUID()
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Updated,
- context
- )
+ const response = await OCPP20ServiceUtils.sendTransactionEvent(
+ mockStation,
+ OCPP20TransactionEventEnumType.Started,
+ OCPP20TriggerReasonEnumType.CablePluggedIn,
+ connectorId,
+ transactionId
+ )
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.SignedDataReceived)
- })
+ // Validate response structure
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ })
- await it('should select appropriate system events for system_event context', () => {
- const testCases = [
- {
- expected: OCPP20TriggerReasonEnumType.EVDeparted,
- systemEvent: 'ev_departed' as const,
- },
- {
- expected: OCPP20TriggerReasonEnumType.EVDetected,
- systemEvent: 'ev_detected' as const,
- },
- {
- expected: OCPP20TriggerReasonEnumType.EVCommunicationLost,
- systemEvent: 'ev_communication_lost' as const,
- },
- {
- expected: OCPP20TriggerReasonEnumType.EVConnectTimeout,
- systemEvent: 'ev_connect_timeout' as const,
+ await it('should handle context-aware error scenarios gracefully', async () => {
+ // Create error mock for this test
+ const { station: errorMockChargingStation } = createMockChargingStation({
+ baseName: TEST_CHARGING_STATION_BASE_NAME,
+ connectorsCount: 1,
+ evseConfiguration: { evsesCount: 1 },
+ heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+ ocppRequestService: {
+ requestHandler: () => {
+ throw new Error('Context test error')
},
- ]
-
- for (const testCase of testCases) {
- const context: OCPP20TransactionContext = {
- source: 'system_event',
- systemEvent: testCase.systemEvent,
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Updated,
- context
- )
-
- assert.strictEqual(triggerReason, testCase.expected)
- }
+ },
+ stationInfo: {
+ ocppStrictCompliance: true,
+ ocppVersion: OCPPVersion.VERSION_201,
+ },
+ websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
})
- await it('should select EnergyLimitReached for energy_limit context', () => {
- const context: OCPP20TransactionContext = {
- source: 'energy_limit',
- }
+ const connectorId = 1
+ const transactionId = generateUUID()
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
+ try {
+ await OCPP20ServiceUtils.sendTransactionEvent(
+ errorMockChargingStation,
OCPP20TransactionEventEnumType.Ended,
- context
+ OCPP20TriggerReasonEnumType.AbnormalCondition,
+ connectorId,
+ transactionId
)
+ throw new Error('Should have thrown error')
+ } catch (error) {
+ assert.ok((error as Error).message.includes('Context test error'))
+ }
+ })
+ })
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EnergyLimitReached)
- })
+ await describe('Backward Compatibility', async () => {
+ await it('should maintain compatibility with existing buildTransactionEvent calls', () => {
+ const connectorId = 1
+ const transactionId = generateUUID()
- await it('should select TimeLimitReached for time_limit context', () => {
- const context: OCPP20TransactionContext = {
- source: 'time_limit',
- }
+ OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- context
- )
+ // Old method call should still work
+ const oldEvent = buildTransactionEvent(
+ mockStation,
+ OCPP20TransactionEventEnumType.Started,
+ OCPP20TriggerReasonEnumType.Authorized,
+ connectorId,
+ transactionId
+ )
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.TimeLimitReached)
- })
+ assert.strictEqual(oldEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(oldEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(oldEvent.seqNo, 0)
+ })
- await it('should select AbnormalCondition for abnormal_condition context', () => {
- const context: OCPP20TransactionContext = {
- abnormalCondition: 'OverCurrent',
- source: 'abnormal_condition',
- }
+ await it('should maintain compatibility with existing sendTransactionEvent calls', async () => {
+ const connectorId = 1
+ const transactionId = generateUUID()
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- context
- )
+ // Old method call should still work
+ const response = await OCPP20ServiceUtils.sendTransactionEvent(
+ mockStation,
+ OCPP20TransactionEventEnumType.Started,
+ OCPP20TriggerReasonEnumType.Authorized,
+ connectorId,
+ transactionId
+ )
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.AbnormalCondition)
- })
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ })
+ })
+ })
- await it('should handle priority ordering with multiple applicable contexts', () => {
- // Test context with multiple applicable triggers - priority should be respected
- const context: OCPP20TransactionContext = {
- cableState: 'plugged_in', // Even lower priority
- command: OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION,
- isDeauthorized: true, // Lower priority but should be overridden
- source: 'remote_command', // High priority
- }
+ // ==========================================================================
+ // Parameterized Transaction Flow Tests (E02, E03, Remote Start)
+ // ==========================================================================
+ await describe('Transaction Flow Patterns', async () => {
+ let mockStation: ChargingStation
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- context
- )
+ beforeEach(() => {
+ const { station } = createMockChargingStation({
+ baseName: TEST_CHARGING_STATION_BASE_NAME,
+ connectorsCount: 3,
+ evseConfiguration: { evsesCount: 3 },
+ heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+ ocppRequestService: {
+ requestHandler: async () => Promise.resolve({} as EmptyObject),
+ },
+ stationInfo: {
+ ocppStrictCompliance: true,
+ ocppVersion: OCPPVersion.VERSION_201,
+ },
+ websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+ })
+ mockStation = station
+ resetLimits(mockStation)
+ })
- // Should select RemoteStart (priority 1) over Deauthorized (priority 2) or CablePluggedIn (priority 3)
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStart)
- })
+ afterEach(() => {
+ standardCleanup()
+ })
- await it('should fallback to Trigger for unknown context source', () => {
- const context: OCPP20TransactionContext = {
- source: 'unknown_source' as OCPP20TransactionContext['source'], // Invalid source to test fallback
- }
+ for (const {
+ description,
+ expectedStartTrigger,
+ id,
+ includeIdToken,
+ name,
+ } of TRANSACTION_FLOWS) {
+ await describe(`${name} Flow`, async () => {
+ await it(`should build correct Started event for ${description}`, () => {
+ const connectorId = 1
+ const transactionId = generateUUID()
+ const idToken: OCPP20IdTokenType | undefined = includeIdToken
+ ? { idToken: `${id.toUpperCase()}_TOKEN_001`, type: OCPP20IdTokenEnumType.ISO14443 }
+ : undefined
+
+ OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
+ const startedEvent = buildTransactionEvent(
+ mockStation,
OCPP20TransactionEventEnumType.Started,
- context
+ expectedStartTrigger,
+ connectorId,
+ transactionId,
+ idToken != null ? { idToken } : undefined
)
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
- })
+ assert.strictEqual(startedEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(startedEvent.triggerReason, expectedStartTrigger)
+ assert.strictEqual(startedEvent.seqNo, 0)
+ assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
- await it('should fallback to Trigger for incomplete context', () => {
- const context: OCPP20TransactionContext = {
- source: 'remote_command',
- // Missing command field
+ if (includeIdToken) {
+ assert.notStrictEqual(startedEvent.idToken, undefined)
+ assert.strictEqual(startedEvent.idToken?.idToken, `${id.toUpperCase()}_TOKEN_001`)
}
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
})
- })
- await describe('buildTransactionEvent with context parameter', async () => {
- await it('should build TransactionEvent with auto-selected TriggerReason from context', () => {
+ await it(`should support complete ${description} transaction lifecycle`, () => {
const connectorId = 1
const transactionId = generateUUID()
- const context: OCPP20TransactionContext = {
- command: OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION,
- source: 'remote_command',
- }
+ const idToken: OCPP20IdTokenType | undefined = includeIdToken
+ ? {
+ idToken: `${id.toUpperCase()}_LIFECYCLE_001`,
+ type: OCPP20IdTokenEnumType.ISO14443,
+ }
+ : undefined
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
- const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ // Step 1: Started event
+ const startedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
- context,
+ expectedStartTrigger,
connectorId,
- transactionId
- )
-
- assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Started)
- assert.strictEqual(
- transactionEvent.triggerReason,
- OCPP20TriggerReasonEnumType.RemoteStart
+ transactionId,
+ idToken != null ? { idToken } : undefined
)
- assert.strictEqual(transactionEvent.seqNo, 0)
- assert.strictEqual(transactionEvent.transactionInfo.transactionId, transactionId)
- })
-
- await it('should pass through optional parameters correctly', () => {
- const connectorId = 2
- const transactionId = generateUUID()
- const context: OCPP20TransactionContext = {
- authorizationMethod: 'idToken',
- source: 'local_authorization',
- }
- const options = {
- chargingState: OCPP20ChargingStateEnumType.Charging,
- idToken: {
- idToken: 'CONTEXT_TEST_TOKEN',
- type: OCPP20IdTokenEnumType.ISO14443,
- },
- }
- const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ // Step 2: Charging state change
+ const chargingEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
- context,
+ OCPP20TriggerReasonEnumType.ChargingStateChanged,
connectorId,
transactionId,
- options
- )
-
- assert.strictEqual(transactionEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
- assert.strictEqual(transactionEvent.idToken?.idToken, 'CONTEXT_TEST_TOKEN')
- assert.strictEqual(
- transactionEvent.transactionInfo.chargingState,
- OCPP20ChargingStateEnumType.Charging
+ { chargingState: OCPP20ChargingStateEnumType.Charging }
)
- })
- })
-
- await describe('sendTransactionEvent with context parameter', async () => {
- await it('should send TransactionEvent with context-aware TriggerReason selection', async () => {
- const connectorId = 1
- const transactionId = generateUUID()
- const context: OCPP20TransactionContext = {
- cableState: 'plugged_in',
- source: 'cable_action',
- }
- const response = await OCPP20ServiceUtils.sendTransactionEvent(
+ // Step 3: Ended event
+ const endedEvent = buildTransactionEvent(
mockStation,
- OCPP20TransactionEventEnumType.Started,
- context,
+ OCPP20TransactionEventEnumType.Ended,
+ OCPP20TriggerReasonEnumType.StopAuthorized,
connectorId,
transactionId
)
- // Validate response structure
- assert.notStrictEqual(response, undefined)
- assert.strictEqual(typeof response, 'object')
- })
-
- await it('should handle context-aware error scenarios gracefully', async () => {
- // Create error mock for this test
- const { station: errorMockChargingStation } = createMockChargingStation({
- baseName: TEST_CHARGING_STATION_BASE_NAME,
- connectorsCount: 1,
- evseConfiguration: { evsesCount: 1 },
- heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
- ocppRequestService: {
- requestHandler: () => {
- throw new Error('Context test error')
- },
- },
- stationInfo: {
- ocppStrictCompliance: true,
- ocppVersion: OCPPVersion.VERSION_201,
- },
- websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
- })
-
- const connectorId = 1
- const transactionId = generateUUID()
- const context: OCPP20TransactionContext = {
- abnormalCondition: 'TestError',
- source: 'abnormal_condition',
- }
+ // Validate event sequence
+ assert.strictEqual(startedEvent.seqNo, 0)
+ assert.strictEqual(chargingEvent.seqNo, 1)
+ assert.strictEqual(endedEvent.seqNo, 2)
- try {
- await OCPP20ServiceUtils.sendTransactionEvent(
- errorMockChargingStation,
- OCPP20TransactionEventEnumType.Ended,
- context,
- connectorId,
- transactionId
- )
- throw new Error('Should have thrown error')
- } catch (error) {
- assert.ok((error as Error).message.includes('Context test error'))
- }
+ // All events share same transaction ID
+ assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(chargingEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(endedEvent.transactionInfo.transactionId, transactionId)
})
- })
- await describe('Backward Compatibility', async () => {
- await it('should maintain compatibility with existing buildTransactionEvent calls', () => {
- const connectorId = 1
- const transactionId = generateUUID()
+ await it(`should maintain independent sequence numbers on different connectors for ${description}`, () => {
+ const connector1 = 1
+ const connector2 = 2
+ const transaction1Id = generateUUID()
+ const transaction2Id = generateUUID()
- OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
+ OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connector1)
+ OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connector2)
- // Old method call should still work
- const oldEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ // Start transaction on connector 1
+ const conn1Event1 = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
- OCPP20TriggerReasonEnumType.Authorized,
- connectorId,
- transactionId
+ expectedStartTrigger,
+ connector1,
+ transaction1Id
)
- assert.strictEqual(oldEvent.eventType, OCPP20TransactionEventEnumType.Started)
- assert.strictEqual(oldEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
- assert.strictEqual(oldEvent.seqNo, 0)
- })
-
- await it('should maintain compatibility with existing sendTransactionEvent calls', async () => {
- const connectorId = 1
- const transactionId = generateUUID()
-
- // Old method call should still work
- const response = await OCPP20ServiceUtils.sendTransactionEvent(
+ // Start transaction on connector 2
+ const conn2Event1 = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
- OCPP20TriggerReasonEnumType.Authorized,
- connectorId,
- transactionId
+ expectedStartTrigger,
+ connector2,
+ transaction2Id
)
- assert.notStrictEqual(response, undefined)
- assert.strictEqual(typeof response, 'object')
- })
- })
- })
-
- // ==========================================================================
- // Parameterized Transaction Flow Tests (E02, E03, Remote Start)
- // ==========================================================================
- await describe('Transaction Flow Patterns', async () => {
- for (const {
- description,
- expectedStartTrigger,
- id,
- includeIdToken,
- name,
- startContext,
- } of TRANSACTION_FLOWS) {
- await describe(`${name} Flow`, async () => {
- await it(`should select ${expectedStartTrigger} trigger for ${description} transaction start`, () => {
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- startContext
- )
- assert.strictEqual(triggerReason, expectedStartTrigger)
- })
-
- await it(`should build correct Started event for ${description}`, () => {
- const connectorId = 1
- const transactionId = generateUUID()
- const idToken: OCPP20IdTokenType | undefined = includeIdToken
- ? { idToken: `${id.toUpperCase()}_TOKEN_001`, type: OCPP20IdTokenEnumType.ISO14443 }
- : undefined
-
- OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
-
- const startedEvent = OCPP20ServiceUtils.buildTransactionEvent(
- mockStation,
- OCPP20TransactionEventEnumType.Started,
- expectedStartTrigger,
- connectorId,
- transactionId,
- idToken != null ? { idToken } : undefined
- )
-
- assert.strictEqual(startedEvent.eventType, OCPP20TransactionEventEnumType.Started)
- assert.strictEqual(startedEvent.triggerReason, expectedStartTrigger)
- assert.strictEqual(startedEvent.seqNo, 0)
- assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
-
- if (includeIdToken) {
- assert.notStrictEqual(startedEvent.idToken, undefined)
- assert.strictEqual(startedEvent.idToken?.idToken, `${id.toUpperCase()}_TOKEN_001`)
- }
- })
-
- await it(`should support complete ${description} transaction lifecycle`, () => {
- const connectorId = 1
- const transactionId = generateUUID()
- const idToken: OCPP20IdTokenType | undefined = includeIdToken
- ? {
- idToken: `${id.toUpperCase()}_LIFECYCLE_001`,
- type: OCPP20IdTokenEnumType.ISO14443,
- }
- : undefined
-
- OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
-
- // Step 1: Started event
- const startedEvent = OCPP20ServiceUtils.buildTransactionEvent(
- mockStation,
- OCPP20TransactionEventEnumType.Started,
- expectedStartTrigger,
- connectorId,
- transactionId,
- idToken != null ? { idToken } : undefined
- )
-
- // Step 2: Charging state change
- const chargingEvent = OCPP20ServiceUtils.buildTransactionEvent(
- mockStation,
- OCPP20TransactionEventEnumType.Updated,
- OCPP20TriggerReasonEnumType.ChargingStateChanged,
- connectorId,
- transactionId,
- { chargingState: OCPP20ChargingStateEnumType.Charging }
- )
-
- // Step 3: Ended event
- const endedEvent = OCPP20ServiceUtils.buildTransactionEvent(
- mockStation,
- OCPP20TransactionEventEnumType.Ended,
- OCPP20TriggerReasonEnumType.StopAuthorized,
- connectorId,
- transactionId
- )
-
- // Validate event sequence
- assert.strictEqual(startedEvent.seqNo, 0)
- assert.strictEqual(chargingEvent.seqNo, 1)
- assert.strictEqual(endedEvent.seqNo, 2)
-
- // All events share same transaction ID
- assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
- assert.strictEqual(chargingEvent.transactionInfo.transactionId, transactionId)
- assert.strictEqual(endedEvent.transactionInfo.transactionId, transactionId)
- })
-
- await it(`should maintain independent sequence numbers on different connectors for ${description}`, () => {
- const connector1 = 1
- const connector2 = 2
- const transaction1Id = generateUUID()
- const transaction2Id = generateUUID()
-
- OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connector1)
- OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connector2)
-
- // Start transaction on connector 1
- const conn1Event1 = OCPP20ServiceUtils.buildTransactionEvent(
- mockStation,
- OCPP20TransactionEventEnumType.Started,
- expectedStartTrigger,
- connector1,
- transaction1Id
- )
+ // Update connector 1
+ const conn1Event2 = buildTransactionEvent(
+ mockStation,
+ OCPP20TransactionEventEnumType.Updated,
+ OCPP20TriggerReasonEnumType.ChargingStateChanged,
+ connector1,
+ transaction1Id
+ )
- // Start transaction on connector 2
- const conn2Event1 = OCPP20ServiceUtils.buildTransactionEvent(
- mockStation,
- OCPP20TransactionEventEnumType.Started,
- expectedStartTrigger,
- connector2,
- transaction2Id
- )
+ // Update connector 2
+ const conn2Event2 = buildTransactionEvent(
+ mockStation,
+ OCPP20TransactionEventEnumType.Updated,
+ OCPP20TriggerReasonEnumType.ChargingStateChanged,
+ connector2,
+ transaction2Id
+ )
- // Update connector 1
- const conn1Event2 = OCPP20ServiceUtils.buildTransactionEvent(
- mockStation,
- OCPP20TransactionEventEnumType.Updated,
- OCPP20TriggerReasonEnumType.ChargingStateChanged,
- connector1,
- transaction1Id
- )
+ // Verify independent sequence numbers
+ assert.strictEqual(conn1Event1.seqNo, 0)
+ assert.strictEqual(conn1Event2.seqNo, 1)
+ assert.strictEqual(conn2Event1.seqNo, 0)
+ assert.strictEqual(conn2Event2.seqNo, 1)
- // Update connector 2
- const conn2Event2 = OCPP20ServiceUtils.buildTransactionEvent(
- mockStation,
- OCPP20TransactionEventEnumType.Updated,
- OCPP20TriggerReasonEnumType.ChargingStateChanged,
- connector2,
- transaction2Id
- )
-
- // Verify independent sequence numbers
- assert.strictEqual(conn1Event1.seqNo, 0)
- assert.strictEqual(conn1Event2.seqNo, 1)
- assert.strictEqual(conn2Event1.seqNo, 0)
- assert.strictEqual(conn2Event2.seqNo, 1)
-
- // Verify independent transaction IDs
- assert.strictEqual(conn1Event1.transactionInfo.transactionId, transaction1Id)
- assert.strictEqual(conn2Event1.transactionInfo.transactionId, transaction2Id)
- })
+ // Verify independent transaction IDs
+ assert.strictEqual(conn1Event1.transactionInfo.transactionId, transaction1Id)
+ assert.strictEqual(conn2Event1.transactionInfo.transactionId, transaction2Id)
})
- }
- })
+ })
+ }
// ==========================================================================
// E02 Cable-First Specific Tests
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// Step 1: Cable plugged in (Started)
- const cablePluggedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const cablePluggedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.CablePluggedIn,
)
// Step 2: EV detected (Updated)
- const evDetectedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const evDetectedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.EVDetected,
)
// Step 3: Charging starts (Updated with ChargingStateChanged)
- const chargingStartedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const chargingStartedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// Start transaction with cable plug
- const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const startEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.CablePluggedIn,
)
// End transaction with EV departure (cable removal)
- const endEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const endEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Ended,
OCPP20TriggerReasonEnumType.EVDeparted,
// Build full cable-first flow
const events = [
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.CablePluggedIn,
connectorId,
transactionId
),
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.EVDetected,
connectorId,
transactionId
),
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.Authorized,
connectorId,
transactionId
),
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
// Cable-first flow with suspended state
const events = [
// 1. Cable plugged
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.CablePluggedIn,
transactionId
),
// 2. Start charging
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
{ chargingState: OCPP20ChargingStateEnumType.Charging }
),
// 3. Suspended by EV
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
{ chargingState: OCPP20ChargingStateEnumType.SuspendedEV }
),
// 4. Resume charging
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
{ chargingState: OCPP20ChargingStateEnumType.Charging }
),
// 5. EV departed
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Ended,
OCPP20TriggerReasonEnumType.EVDeparted,
})
})
- await describe('Context-Based Cable Event Trigger Selection', async () => {
- await it('should select CablePluggedIn from cable_action context with plugged_in state', () => {
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- TransactionContextFixtures.cablePluggedIn()
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn)
- })
-
- await it('should select EVDetected from cable_action context with detected state', () => {
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Updated,
- TransactionContextFixtures.evDetected()
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDetected)
- })
-
- await it('should select EVDeparted from cable_action context with unplugged state', () => {
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- TransactionContextFixtures.evDeparted()
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDeparted)
- })
- })
- })
-
- // ==========================================================================
- // E03 IdToken-First Specific Tests
- // ==========================================================================
- await describe('E03 - IdToken-First Pre-Authorization', async () => {
- beforeEach(() => {
- resetConnectorTransactionState(mockStation)
- })
-
- await describe('E03.FR.13 - Trigger Reason Selection', async () => {
- await it('should select groupIdToken trigger for group authorization', () => {
- const context: OCPP20TransactionContext = {
- authorizationMethod: 'groupIdToken',
- source: 'local_authorization',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Authorized)
- })
-
- await it('should differentiate IdToken-first from Cable-first by trigger reason', () => {
- // IdToken-first: Authorized trigger
- const idTokenFirstContext: OCPP20TransactionContext = {
- authorizationMethod: 'idToken',
- source: 'local_authorization',
- }
-
- // Cable-first: CablePluggedIn trigger
- const cableFirstContext: OCPP20TransactionContext = {
- cableState: 'plugged_in',
- source: 'cable_action',
- }
-
- const idTokenTrigger = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- idTokenFirstContext
- )
-
- const cableTrigger = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Started,
- cableFirstContext
- )
-
- assert.strictEqual(idTokenTrigger, OCPP20TriggerReasonEnumType.Authorized)
- assert.strictEqual(cableTrigger, OCPP20TriggerReasonEnumType.CablePluggedIn)
- assert.notStrictEqual(idTokenTrigger, cableTrigger)
- })
- })
-
await describe('E03.FR.01 - IdToken in TransactionEvent', async () => {
await it('should include idToken in first TransactionEvent after authorization', () => {
const connectorId = 1
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// Build Started event with idToken (E03.FR.01: IdToken must be in first event)
- const startedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const startedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// First event includes idToken
- const startedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const startedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
)
// Second event should NOT include idToken (flag is set after first inclusion)
- const updatedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const updatedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
type: OCPP20IdTokenEnumType.ISO14443,
}
- const rfidEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const rfidEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
type: OCPP20IdTokenEnumType.eMAID,
}
- const emaidEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const emaidEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// E03 Step 1: IdToken presented and authorized (Started with Authorized trigger)
- const authorizedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const authorizedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
)
// E03 Step 2: Cable connected (Updated event)
- const cableConnectedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const cableConnectedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.CablePluggedIn,
)
// E03 Step 3: Charging starts
- const chargingEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const chargingEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
)
// E03 Step 4: Transaction ends
- const endedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const endedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Ended,
OCPP20TriggerReasonEnumType.StopAuthorized,
connectorStatus.transactionIdTokenSent = undefined
}
- const e03Start = OCPP20ServiceUtils.buildTransactionEvent(
+ const e03Start = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
connectorStatus.transactionIdTokenSent = undefined
}
- const e02Start = OCPP20ServiceUtils.buildTransactionEvent(
+ const e02Start = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.CablePluggedIn,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// E03.FR.05: User authorizes with IdToken
- const authorizedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const authorizedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
)
// E03.FR.06: Cable not connected within timeout - transaction ends with Timeout
- const timeoutEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const timeoutEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Ended,
OCPP20TriggerReasonEnumType.EVConnectTimeout,
})
await describe('Authorization Status in E03', async () => {
- await it('should support Deauthorized trigger for rejected authorization', () => {
- const context: OCPP20TransactionContext = {
- authorizationMethod: 'idToken',
- isDeauthorized: true,
- source: 'local_authorization',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
- })
-
await it('should handle transaction end after token revocation', () => {
const connectorId = 1
const transactionId = generateUUID()
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// Transaction started with authorization
- const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const startEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
)
// Transaction ended due to deauthorization (e.g., token revoked mid-session)
- const revokedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const revokedEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Ended,
OCPP20TriggerReasonEnumType.Deauthorized,
assert.strictEqual(revokedEvent.eventType, OCPP20TransactionEventEnumType.Ended)
assert.strictEqual(revokedEvent.triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
})
-
- await it('should support StopAuthorized trigger for normal transaction end', () => {
- const context: OCPP20TransactionContext = {
- authorizationMethod: 'stopAuthorized',
- source: 'local_authorization',
- }
-
- const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
- OCPP20TransactionEventEnumType.Ended,
- context
- )
-
- assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.StopAuthorized)
- })
})
await describe('E03.FR.07/08 - Sequence Numbers and Transaction ID', async () => {
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
const events = [
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
transactionId,
{ idToken }
),
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.CablePluggedIn,
connectorId,
transactionId
),
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.ChargingStateChanged,
connectorId,
transactionId
),
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.MeterValuePeriodic,
connectorId,
transactionId
),
- OCPP20ServiceUtils.buildTransactionEvent(
+ buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Ended,
OCPP20TriggerReasonEnumType.StopAuthorized,
// E03.FR.08: transactionId MUST be unique
assert.notStrictEqual(transaction1Id, transaction2Id)
- const event1 = OCPP20ServiceUtils.buildTransactionEvent(
+ const event1 = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
- const event2 = OCPP20ServiceUtils.buildTransactionEvent(
+ const event2 = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// Send initial Started event
- const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const startEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
// Send multiple periodic events (simulating timer ticks)
for (let i = 1; i <= 3; i++) {
- const periodicEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const periodicEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.MeterValuePeriodic,
// Simulate full transaction lifecycle with periodic updates
// 1. Started event (seqNo: 0)
- const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const startEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
// 2. Multiple periodic updates (seqNo: 1, 2, 3)
for (let i = 1; i <= 3; i++) {
- const updateEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const updateEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.MeterValuePeriodic,
}
// 3. Ended event (seqNo: 4)
- const endEvent = OCPP20ServiceUtils.buildTransactionEvent(
+ const endEvent = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Ended,
OCPP20TriggerReasonEnumType.StopAuthorized,
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, 2)
// Build events for connector 1
- const event1Start = OCPP20ServiceUtils.buildTransactionEvent(
+ const event1Start = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
1,
transactionId1
)
- const event1Update = OCPP20ServiceUtils.buildTransactionEvent(
+ const event1Update = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.MeterValuePeriodic,
)
// Build events for connector 2
- const event2Start = OCPP20ServiceUtils.buildTransactionEvent(
+ const event2Start = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.Authorized,
2,
transactionId2
)
- const event2Update = OCPP20ServiceUtils.buildTransactionEvent(
+ const event2Update = buildTransactionEvent(
mockStation,
OCPP20TransactionEventEnumType.Updated,
OCPP20TriggerReasonEnumType.MeterValuePeriodic,