- **Message format**: SRPC format: `[messageTypeId, messageId, action, payload]`
- **Per-station state**: WeakMap-based isolation per station (not singleton properties)
+### Request Architecture
+
+- **Single path**: `requestHandler()` → `buildRequestPayload()` → `sendMessage()` — no bypasses
+- **`buildRequestPayload()`** enriches where needed (1.6: meterStart/idTag/timestamp; 2.0: CSR generation), passthrough otherwise
+- **Service utils** (`buildTransactionEvent`, `buildStatusNotificationRequest`, `buildMeterValue`, etc.) build complex payloads upstream before calling `requestHandler`
+- **Broadcast channel handlers** are simple passthroughs to `requestHandler` — no state management or flow duplication
+
## Logging
- **Winston** logger with 4 levels: `error`, `warn`, `info`, `debug`
} from '../../../utils/index.js'
import { OCPPConstants } from '../OCPPConstants.js'
import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
-import {
- buildMeterValue,
- buildStatusNotificationRequest,
- OCPPServiceUtils,
-} from '../OCPPServiceUtils.js'
+import { buildMeterValue, OCPPServiceUtils } from '../OCPPServiceUtils.js'
import { OCPP16Constants } from './OCPP16Constants.js'
import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
.requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
chargingStation,
OCPP16RequestCommand.STATUS_NOTIFICATION,
- buildStatusNotificationRequest(
- chargingStation,
+ {
connectorId,
- chargingStation.getConnectorStatus(connectorId)
- ?.status as OCPP16ChargePointStatus
- ) as OCPP16StatusNotificationRequest,
+ status: chargingStation.getConnectorStatus(connectorId)
+ ?.status as OCPP16ChargePointStatus,
+ } as unknown as OCPP16StatusNotificationRequest,
{
triggerMessage: true,
}
>(
chargingStation,
OCPP16RequestCommand.STATUS_NOTIFICATION,
- buildStatusNotificationRequest(
- chargingStation,
- id,
- connectorStatus.status as OCPP16ChargePointStatus
- ) as OCPP16StatusNotificationRequest,
+ {
+ connectorId: id,
+ status: connectorStatus.status as OCPP16ChargePointStatus,
+ } as unknown as OCPP16StatusNotificationRequest,
{
triggerMessage: true,
}
>(
chargingStation,
OCPP16RequestCommand.STATUS_NOTIFICATION,
- buildStatusNotificationRequest(
- chargingStation,
- id,
- connectorStatus.status as OCPP16ChargePointStatus
- ) as OCPP16StatusNotificationRequest,
+ {
+ connectorId: id,
+ status: connectorStatus.status as OCPP16ChargePointStatus,
+ } as unknown as OCPP16StatusNotificationRequest,
{
triggerMessage: true,
}
import { OCPPError } from '../../../exception/index.js'
import {
+ type ConnectorStatusEnum,
ErrorType,
type JsonObject,
type JsonType,
} from '../../../types/index.js'
import { Constants, generateUUID, logger } from '../../../utils/index.js'
import { OCPPRequestService } from '../OCPPRequestService.js'
+import { buildStatusNotificationRequest } from '../OCPPServiceUtils.js'
import { OCPP16Constants } from './OCPP16Constants.js'
import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
case OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION:
case OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION:
case OCPP16RequestCommand.METER_VALUES:
- case OCPP16RequestCommand.STATUS_NOTIFICATION:
return commandParams as unknown as Request
case OCPP16RequestCommand.HEARTBEAT:
return OCPP16Constants.OCPP_REQUEST_EMPTY as unknown as Request
}),
...commandParams,
} as unknown as Request
+ case OCPP16RequestCommand.STATUS_NOTIFICATION:
+ if (commandParams.errorCode != null) {
+ return commandParams as unknown as Request
+ }
+ return buildStatusNotificationRequest(
+ chargingStation,
+ commandParams.connectorId as number,
+ commandParams.status as ConnectorStatusEnum,
+ commandParams.evseId as number | undefined
+ ) as unknown as Request
case OCPP16RequestCommand.STOP_TRANSACTION:
chargingStation.stationInfo?.transactionDataMeterValues === true &&
(connectorId = chargingStation.getConnectorIdByTransactionId(
import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
import {
buildMeterValue,
- buildStatusNotificationRequest,
restoreConnectorStatus,
sendAndSetConnectorStatus,
} from '../OCPPServiceUtils.js'
.requestHandler<
OCPP20StatusNotificationRequest,
OCPP20StatusNotificationResponse
- >(chargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, buildStatusNotificationRequest(chargingStation, connectorId, resolvedStatus, evseId) as OCPP20StatusNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
+ >(chargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, { connectorId, evseId, status: resolvedStatus } as unknown as OCPP20StatusNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
.catch(errorHandler)
}
}
.requestHandler<
OCPP20StatusNotificationRequest,
OCPP20StatusNotificationResponse
- >(chargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, buildStatusNotificationRequest(chargingStation, evse.connectorId, resolvedStatus, evse.id) as OCPP20StatusNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
+ >(chargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, { connectorId: evse.connectorId, evseId: evse.id, status: resolvedStatus } as unknown as OCPP20StatusNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
.catch(errorHandler)
} else if (chargingStation.hasEvses) {
this.triggerAllEvseStatusNotifications(chargingStation, errorHandler)
import type { ValidateFunction } from 'ajv'
import type { ChargingStation } from '../../../charging-station/index.js'
+import type {
+ ConnectorStatusEnum,
+ OCPP20TransactionEventEnumType,
+ OCPP20TriggerReasonEnumType,
+} from '../../../types/index.js'
+import type { OCPP20TransactionEventOptions } from '../../../types/ocpp/2.0/Transaction.js'
import type { OCPPResponseService } from '../OCPPResponseService.js'
import { OCPPError } from '../../../exception/index.js'
} from '../../../types/index.js'
import { generateUUID, logger } from '../../../utils/index.js'
import { OCPPRequestService } from '../OCPPRequestService.js'
+import { buildStatusNotificationRequest } from '../OCPPServiceUtils.js'
import { generatePkcs10Csr } from './Asn1DerUtils.js'
import { OCPP20Constants } from './OCPP20Constants.js'
import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
case OCPP20RequestCommand.NOTIFY_CUSTOMER_INFORMATION:
case OCPP20RequestCommand.NOTIFY_REPORT:
case OCPP20RequestCommand.SECURITY_EVENT_NOTIFICATION:
- case OCPP20RequestCommand.STATUS_NOTIFICATION:
- case OCPP20RequestCommand.TRANSACTION_EVENT:
return commandParams as unknown as Request
case OCPP20RequestCommand.HEARTBEAT:
return OCPP20Constants.OCPP_RESPONSE_EMPTY as unknown as Request
return requestPayload as unknown as Request
}
+ case OCPP20RequestCommand.STATUS_NOTIFICATION:
+ return buildStatusNotificationRequest(
+ chargingStation,
+ commandParams.connectorId as number,
+ commandParams.status as ConnectorStatusEnum,
+ commandParams.evseId as number | undefined
+ ) as unknown as Request
+ case OCPP20RequestCommand.TRANSACTION_EVENT: {
+ // Pre-built payloads (e.g., from offline queue) already have transactionInfo;
+ // pass them through as-is to avoid double-building.
+ if (commandParams.transactionInfo != null) {
+ return commandParams as unknown as Request
+ }
+ const eventType = commandParams.eventType as OCPP20TransactionEventEnumType
+ const triggerReason = commandParams.triggerReason as OCPP20TriggerReasonEnumType
+ const connectorId = commandParams.connectorId as number
+ const transactionId = commandParams.transactionId as string
+ return OCPP20ServiceUtils.buildTransactionEvent(
+ chargingStation,
+ eventType,
+ triggerReason,
+ connectorId,
+ transactionId,
+ commandParams as unknown as OCPP20TransactionEventOptions
+ ) as unknown as Request
+ }
default: {
// OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
const errorMsg = `Unsupported OCPP command ${commandName as string} for payload building`
? this.selectTriggerReason(eventType, triggerReasonOrContext)
: triggerReasonOrContext
- // Build the transaction event request
- const transactionEventRequest = OCPP20ServiceUtils.buildTransactionEvent(
- chargingStation,
- eventType,
- triggerReason,
- connectorId,
- transactionId,
- options
- )
-
- // OCPP 2.0.1 offline-first: Queue event if offline, send if online
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
if (connectorStatus == null) {
const errorMsg = `Cannot find connector status for connector ${connectorId.toString()}`
throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
}
+ // Offline path: build payload directly for queueing (queue stores pre-built requests
+ // that are sent as-is on reconnect, bypassing buildRequestPayload).
if (!chargingStation.isWebSocketConnectionOpened()) {
+ const transactionEventRequest = OCPP20ServiceUtils.buildTransactionEvent(
+ chargingStation,
+ eventType,
+ triggerReason,
+ connectorId,
+ transactionId,
+ options
+ )
logger.info(
`${chargingStation.logPrefix()} ${moduleName}.sendTransactionEvent: Station offline, queueing TransactionEvent with seqNo=${transactionEventRequest.seqNo.toString()}`
)
return { idTokenInfo: undefined }
}
+ // Online path: pass minimal params to requestHandler → buildRequestPayload builds
logger.debug(
`${chargingStation.logPrefix()} ${moduleName}.sendTransactionEvent: Sending TransactionEvent for trigger ${triggerReason}`
)
const response = await chargingStation.ocppRequestService.requestHandler<
OCPP20TransactionEventRequest,
OCPP20TransactionEventResponse
- >(chargingStation, OCPP20RequestCommand.TRANSACTION_EVENT, transactionEventRequest)
+ >(chargingStation, OCPP20RequestCommand.TRANSACTION_EVENT, {
+ connectorId,
+ eventType,
+ transactionId,
+ triggerReason,
+ ...options,
+ } as unknown as OCPP20TransactionEventRequest)
return response
} catch (error) {
await chargingStation.ocppRequestService.requestHandler<
StatusNotificationRequest,
StatusNotificationResponse
- >(
- chargingStation,
- RequestCommand.STATUS_NOTIFICATION,
- buildStatusNotificationRequest(chargingStation, connectorId, status, evseId)
- )
+ >(chargingStation, RequestCommand.STATUS_NOTIFICATION, {
+ connectorId,
+ evseId,
+ status,
+ } as unknown as StatusNotificationRequest)
}
connectorStatus.status = status
chargingStation.emitChargingStationEvent(ChargingStationEvents.connectorStatusChanged, {
const args = requestHandlerMock.mock.calls[0].arguments as [
unknown,
string,
- OCPP20TransactionEventRequest
+ Record<string, unknown>
]
- const transactionEvent = args[2]
-
- assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Ended)
- assert.notStrictEqual(transactionEvent.timestamp, undefined)
- assert.ok(transactionEvent.timestamp instanceof Date)
- assert.strictEqual(transactionEvent.triggerReason, OCPP20TriggerReasonEnumType.RemoteStop)
- assert.notStrictEqual(transactionEvent.seqNo, undefined)
- assert.strictEqual(typeof transactionEvent.seqNo, 'number')
-
- assert.notStrictEqual(transactionEvent.transactionInfo, undefined)
- assert.strictEqual(transactionEvent.transactionInfo.transactionId, transactionId)
- assert.strictEqual(
- transactionEvent.transactionInfo.stoppedReason,
- OCPP20ReasonEnumType.Remote
- )
-
- assert.notStrictEqual(transactionEvent.evse, undefined)
- assert.strictEqual(transactionEvent.evse?.id, 2)
+ const minimalParams = args[2]
+
+ assert.strictEqual(minimalParams.eventType, OCPP20TransactionEventEnumType.Ended)
+ assert.strictEqual(minimalParams.triggerReason, OCPP20TriggerReasonEnumType.RemoteStop)
+ assert.strictEqual(minimalParams.transactionId, transactionId)
+ assert.strictEqual(minimalParams.stoppedReason, OCPP20ReasonEnumType.Remote)
+ assert.notStrictEqual(minimalParams.connectorId, undefined)
+ assert.strictEqual(typeof minimalParams.connectorId, 'number')
})
// FR: F03.FR.09
assert.notStrictEqual(payload, undefined)
assert.ok('evseId' in payload, 'Expected payload to include evseId')
assert.ok('connectorId' in payload, 'Expected payload to include connectorId')
- assert.ok('connectorStatus' in payload, 'Expected payload to include connectorStatus')
- assert.ok('timestamp' in payload, 'Expected payload to include timestamp')
+ assert.ok('status' in payload, 'Expected payload to include status')
assert.ok((payload.evseId as number) > 0, 'Expected evseId > 0 (EVSE 0 excluded)')
assert.strictEqual(options.skipBufferingOnError, true)
assert.strictEqual(options.triggerMessage, true)
assert.strictEqual(command, OCPP20RequestCommand.STATUS_NOTIFICATION)
assert.strictEqual(payload.evseId, 1)
assert.strictEqual(payload.connectorId, 1)
- assert.ok('connectorStatus' in payload)
- assert.ok(payload.timestamp instanceof Date)
+ assert.ok('status' in payload)
assert.strictEqual(options.skipBufferingOnError, true)
assert.strictEqual(options.triggerMessage, true)
})
import type { ChargingStation } from '../../../../src/charging-station/index.js'
import {
+ ConnectorStatusEnum,
OCPP20ConnectorStatusEnumType,
OCPP20RequestCommand,
type OCPP20StatusNotificationRequest,
// FR: G01.FR.01
await it('should build StatusNotification request payload correctly with Available status', () => {
- const testTimestamp = new Date('2024-01-15T10:30:00.000Z')
-
- const requestParams: OCPP20StatusNotificationRequest = {
- connectorId: 1,
- connectorStatus: OCPP20ConnectorStatusEnumType.Available,
- evseId: 1,
- timestamp: testTimestamp,
- }
-
const payload = testableRequestService.buildRequestPayload(
station,
OCPP20RequestCommand.STATUS_NOTIFICATION,
- requestParams
+ {
+ connectorId: 1,
+ evseId: 1,
+ status: ConnectorStatusEnum.Available,
+ }
) as OCPP20StatusNotificationRequest
assert.notStrictEqual(payload, undefined)
assert.strictEqual(payload.connectorId, 1)
assert.strictEqual(payload.connectorStatus, OCPP20ConnectorStatusEnumType.Available)
assert.strictEqual(payload.evseId, 1)
- assert.strictEqual(payload.timestamp, testTimestamp)
+ assert.ok(payload.timestamp instanceof Date)
})
// FR: G01.FR.02
await it('should build StatusNotification request payload correctly with Occupied status', () => {
- const testTimestamp = new Date('2024-01-15T11:45:30.000Z')
-
- const requestParams: OCPP20StatusNotificationRequest = {
- connectorId: 2,
- connectorStatus: OCPP20ConnectorStatusEnumType.Occupied,
- evseId: 2,
- timestamp: testTimestamp,
- }
-
const payload = testableRequestService.buildRequestPayload(
station,
OCPP20RequestCommand.STATUS_NOTIFICATION,
- requestParams
+ {
+ connectorId: 2,
+ evseId: 2,
+ status: ConnectorStatusEnum.Occupied,
+ }
) as OCPP20StatusNotificationRequest
assert.notStrictEqual(payload, undefined)
assert.strictEqual(payload.connectorId, 2)
assert.strictEqual(payload.connectorStatus, OCPP20ConnectorStatusEnumType.Occupied)
assert.strictEqual(payload.evseId, 2)
- assert.strictEqual(payload.timestamp, testTimestamp)
+ assert.ok(payload.timestamp instanceof Date)
})
// FR: G01.FR.03
await it('should build StatusNotification request payload correctly with Faulted status', () => {
- const testTimestamp = new Date('2024-01-15T12:15:45.500Z')
-
- const requestParams: OCPP20StatusNotificationRequest = {
- connectorId: 1,
- connectorStatus: OCPP20ConnectorStatusEnumType.Faulted,
- evseId: 1,
- timestamp: testTimestamp,
- }
-
const payload = testableRequestService.buildRequestPayload(
station,
OCPP20RequestCommand.STATUS_NOTIFICATION,
- requestParams
+ {
+ connectorId: 1,
+ evseId: 1,
+ status: ConnectorStatusEnum.Faulted,
+ }
) as OCPP20StatusNotificationRequest
assert.notStrictEqual(payload, undefined)
assert.strictEqual(payload.connectorId, 1)
assert.strictEqual(payload.connectorStatus, OCPP20ConnectorStatusEnumType.Faulted)
assert.strictEqual(payload.evseId, 1)
- assert.strictEqual(payload.timestamp, testTimestamp)
+ assert.ok(payload.timestamp instanceof Date)
})
// FR: G01.FR.04
await it('should handle all OCPP20ConnectorStatusEnumType values correctly', () => {
- const testTimestamp = new Date('2024-01-15T13:00:00.000Z')
-
- const statusValues = [
- OCPP20ConnectorStatusEnumType.Available,
- OCPP20ConnectorStatusEnumType.Faulted,
- OCPP20ConnectorStatusEnumType.Occupied,
- OCPP20ConnectorStatusEnumType.Reserved,
- OCPP20ConnectorStatusEnumType.Unavailable,
+ const statusValues: [ConnectorStatusEnum, OCPP20ConnectorStatusEnumType][] = [
+ [ConnectorStatusEnum.Available, OCPP20ConnectorStatusEnumType.Available],
+ [ConnectorStatusEnum.Faulted, OCPP20ConnectorStatusEnumType.Faulted],
+ [ConnectorStatusEnum.Occupied, OCPP20ConnectorStatusEnumType.Occupied],
+ [ConnectorStatusEnum.Reserved, OCPP20ConnectorStatusEnumType.Reserved],
+ [ConnectorStatusEnum.Unavailable, OCPP20ConnectorStatusEnumType.Unavailable],
]
- statusValues.forEach((status, index) => {
- const requestParams: OCPP20StatusNotificationRequest = {
- connectorId: index + 1,
- connectorStatus: status,
- evseId: index + 1,
- timestamp: testTimestamp,
- }
-
+ statusValues.forEach(([inputStatus, expectedConnectorStatus], index) => {
const payload = testableRequestService.buildRequestPayload(
station,
OCPP20RequestCommand.STATUS_NOTIFICATION,
- requestParams
+ {
+ connectorId: index + 1,
+ evseId: index + 1,
+ status: inputStatus,
+ }
) as OCPP20StatusNotificationRequest
assert.notStrictEqual(payload, undefined)
- assert.strictEqual(payload.connectorStatus, status)
+ assert.strictEqual(payload.connectorStatus, expectedConnectorStatus)
assert.strictEqual(payload.connectorId, index + 1)
assert.strictEqual(payload.evseId, index + 1)
- assert.strictEqual(payload.timestamp, testTimestamp)
+ assert.ok(payload.timestamp instanceof Date)
})
})
// FR: G01.FR.05
await it('should validate payload structure matches OCPP20StatusNotificationRequest interface', () => {
- const testTimestamp = new Date('2024-01-15T14:30:15.123Z')
-
- const requestParams: OCPP20StatusNotificationRequest = {
- connectorId: 3,
- connectorStatus: OCPP20ConnectorStatusEnumType.Reserved,
- evseId: 2,
- timestamp: testTimestamp,
- }
-
const payload = testableRequestService.buildRequestPayload(
station,
OCPP20RequestCommand.STATUS_NOTIFICATION,
- requestParams
+ {
+ connectorId: 3,
+ evseId: 2,
+ status: ConnectorStatusEnum.Reserved,
+ }
) as OCPP20StatusNotificationRequest
// Validate that the payload has the exact structure of OCPP20StatusNotificationRequest
assert.strictEqual(payload.connectorId, 3)
assert.strictEqual(payload.connectorStatus, OCPP20ConnectorStatusEnumType.Reserved)
assert.strictEqual(payload.evseId, 2)
- assert.strictEqual(payload.timestamp, testTimestamp)
})
// FR: G01.FR.06
await it('should handle edge case connector and EVSE IDs correctly', () => {
- const testTimestamp = new Date('2024-01-15T15:45:00.000Z')
-
// Test with connector ID 0 (valid in OCPP 2.0 for the charging station itself)
- const requestParamsConnector0: OCPP20StatusNotificationRequest = {
- connectorId: 0,
- connectorStatus: OCPP20ConnectorStatusEnumType.Available,
- evseId: 1,
- timestamp: testTimestamp,
- }
-
const payloadConnector0 = testableRequestService.buildRequestPayload(
station,
OCPP20RequestCommand.STATUS_NOTIFICATION,
- requestParamsConnector0
+ {
+ connectorId: 0,
+ evseId: 1,
+ status: ConnectorStatusEnum.Available,
+ }
) as OCPP20StatusNotificationRequest
assert.notStrictEqual(payloadConnector0, undefined)
assert.strictEqual(payloadConnector0.connectorId, 0)
assert.strictEqual(payloadConnector0.connectorStatus, OCPP20ConnectorStatusEnumType.Available)
assert.strictEqual(payloadConnector0.evseId, 1)
- assert.strictEqual(payloadConnector0.timestamp, testTimestamp)
+ assert.ok(payloadConnector0.timestamp instanceof Date)
// Test with EVSE ID 0 (valid in OCPP 2.0 for the charging station itself)
- const requestParamsEvse0: OCPP20StatusNotificationRequest = {
- connectorId: 1,
- connectorStatus: OCPP20ConnectorStatusEnumType.Unavailable,
- evseId: 0,
- timestamp: testTimestamp,
- }
-
const payloadEvse0 = testableRequestService.buildRequestPayload(
station,
OCPP20RequestCommand.STATUS_NOTIFICATION,
- requestParamsEvse0
+ {
+ connectorId: 1,
+ evseId: 0,
+ status: ConnectorStatusEnum.Unavailable,
+ }
) as OCPP20StatusNotificationRequest
assert.notStrictEqual(payloadEvse0, undefined)
assert.strictEqual(payloadEvse0.connectorId, 1)
assert.strictEqual(payloadEvse0.connectorStatus, OCPP20ConnectorStatusEnumType.Unavailable)
assert.strictEqual(payloadEvse0.evseId, 0)
- assert.strictEqual(payloadEvse0.timestamp, testTimestamp)
+ assert.ok(payloadEvse0.timestamp instanceof Date)
})
// FR: G01.FR.07
await it('should handle different timestamp formats correctly', () => {
- const testCases = [
- new Date('2024-01-01T00:00:00.000Z'), // Start of year
- new Date('2024-12-31T23:59:59.999Z'), // End of year
- new Date(), // Current time
- new Date('2024-06-15T12:30:45.678Z'), // Mid-year with milliseconds
+ // buildRequestPayload now generates its own timestamp via buildStatusNotificationRequest,
+ // so we verify the output always has a valid Date timestamp
+ const statusValues = [
+ ConnectorStatusEnum.Available,
+ ConnectorStatusEnum.Occupied,
+ ConnectorStatusEnum.Faulted,
+ ConnectorStatusEnum.Reserved,
]
- testCases.forEach((timestamp, _index) => {
- const requestParams: OCPP20StatusNotificationRequest = {
- connectorId: 1,
- connectorStatus: OCPP20ConnectorStatusEnumType.Available,
- evseId: 1,
- timestamp,
- }
-
+ const beforeBuild = new Date()
+ statusValues.forEach(status => {
const payload = testableRequestService.buildRequestPayload(
station,
OCPP20RequestCommand.STATUS_NOTIFICATION,
- requestParams
+ {
+ connectorId: 1,
+ evseId: 1,
+ status,
+ }
) as OCPP20StatusNotificationRequest
assert.notStrictEqual(payload, undefined)
- assert.strictEqual(payload.timestamp, timestamp)
assert.ok(payload.timestamp instanceof Date)
+ assert.ok(payload.timestamp.getTime() >= beforeBuild.getTime())
})
})
})
)
assert.strictEqual(sentRequests.length, 1)
- assert.strictEqual(sentRequests[0].payload.seqNo, 0)
+ assert.strictEqual(
+ sentRequests[0].payload.eventType,
+ OCPP20TransactionEventEnumType.Started
+ )
setOnline(false)
const connector = mockStation.getConnectorStatus(connectorId)
assert.strictEqual(connector?.transactionEventQueue?.length, 2)
- assert.strictEqual(connector.transactionEventQueue[0].seqNo, 1)
- assert.strictEqual(connector.transactionEventQueue[1].seqNo, 2)
+ // Online path with mock doesn't call buildTransactionEvent, so seqNo starts from 0
+ assert.strictEqual(connector.transactionEventQueue[0].seqNo, 0)
+ assert.strictEqual(connector.transactionEventQueue[1].seqNo, 1)
})
await it('should include timestamp in queued events', async () => {
connectorId,
transactionId
)
- assert.strictEqual(sentRequests[0].payload.seqNo, 0)
+ // Online path sends minimal params (no seqNo in payload)
+ assert.strictEqual(
+ sentRequests[0].payload.eventType,
+ OCPP20TransactionEventEnumType.Started
+ )
setOnline(false)
await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
- assert.strictEqual(sentRequests[1].payload.seqNo, 1)
- assert.strictEqual(sentRequests[2].payload.seqNo, 2)
+ // Queued events are pre-built payloads with seqNo (starts from 0 since
+ // the online path with mock doesn't call buildTransactionEvent)
+ assert.strictEqual(sentRequests[1].payload.seqNo, 0)
+ assert.strictEqual(sentRequests[2].payload.seqNo, 1)
await OCPP20ServiceUtils.sendTransactionEvent(
mockStation,
transactionId
)
- assert.strictEqual(sentRequests[3].payload.seqNo, 3)
+ // Online path sends minimal params (no seqNo in payload)
+ assert.strictEqual(sentRequests[3].payload.eventType, OCPP20TransactionEventEnumType.Ended)
- for (let i = 0; i < sentRequests.length; i++) {
- assert.strictEqual(sentRequests[i].payload.seqNo, i)
- }
+ // Verify seqNo continuity for queued events (indices 1 and 2)
+ assert.strictEqual(sentRequests[1].payload.seqNo, 0)
+ assert.strictEqual(sentRequests[2].payload.seqNo, 1)
+ // Verify total request count
+ assert.strictEqual(sentRequests.length, 4)
})
})
transactionId
)
- // Verify EVSE info is present
- assert.notStrictEqual(sentRequests[0].payload.evse, undefined)
- assert.strictEqual(
- (sentRequests[0].payload.evse as Record<string, unknown>).id,
- connectorId
- )
+ // Online path sends minimal params with connectorId (EVSE resolved by buildRequestPayload)
+ assert.strictEqual(sentRequests[0].payload.connectorId, connectorId)
})
await it('should include transactionInfo with correct transactionId', async () => {
transactionId
)
- // Verify transactionInfo contains the transaction ID
- assert.notStrictEqual(sentRequests[0].payload.transactionInfo, undefined)
- assert.strictEqual(
- (sentRequests[0].payload.transactionInfo as Record<string, unknown>).transactionId,
- transactionId
- )
+ // Online path sends minimal params with transactionId at top level
+ assert.strictEqual(sentRequests[0].payload.transactionId, transactionId)
})
})