this.chargingStation.stationInfo?.ocppVersion === OCPPVersion.VERSION_20 ||
this.chargingStation.stationInfo?.ocppVersion === OCPPVersion.VERSION_201
) {
- const txUpdatedInterval = OCPP20ServiceUtils.getTxUpdatedInterval(this.chargingStation)
+ const alignedDataInterval = OCPP20ServiceUtils.getAlignedDataInterval(this.chargingStation)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const evseId = this.chargingStation.getEvseIdByConnectorId(connectorId!)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
connectorId!,
transactionId,
- txUpdatedInterval
+ alignedDataInterval
),
],
...requestPayload,
if (response.status === RequestStartStopStatusEnumType.Accepted) {
const connectorId = chargingStation.getConnectorIdByTransactionId(response.transactionId)
if (connectorId != null) {
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ const startedMeterValues =
+ connectorStatus != null
+ ? OCPP20ServiceUtils.buildTransactionStartedMeterValues(connectorStatus)
+ : []
OCPP20ServiceUtils.sendTransactionEvent(
chargingStation,
OCPP20TransactionEventEnumType.Started,
OCPP20TriggerReasonEnumType.RemoteStart,
connectorId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- response.transactionId!
+ response.transactionId!,
+ startedMeterValues.length > 0 ? { meterValue: startedMeterValues } : undefined
).catch((error: unknown) => {
logger.error(
`${chargingStation.logPrefix()} ${moduleName}.constructor: TransactionEvent(Started) error:`,
[OCPP20RequestCommand.TRANSACTION_EVENT, 'TransactionEvent'],
]
+ static buildTransactionStartedMeterValues (connectorStatus: ConnectorStatus): OCPP20MeterValue[] {
+ return OCPP20ServiceUtils.buildEnergyMeterValues(
+ connectorStatus,
+ OCPP20ReadingContextEnumType.TRANSACTION_BEGIN
+ )
+ }
+
/**
* OCPP 2.0 Incoming Request Service validator configurations
* @returns Array of validator configuration tuples
return currentResults
}
- /**
- * Gets the TxUpdatedInterval configuration value for periodic TransactionEvent(Updated) messages.
- * Reads the SampledDataCtrlr.TxUpdatedInterval variable and falls back to
- * Constants.DEFAULT_TX_UPDATED_INTERVAL if not configured.
- * @param chargingStation - The charging station instance
- * @returns The interval in milliseconds
- */
+ public static getAlignedDataInterval (chargingStation: ChargingStation): number {
+ return OCPP20ServiceUtils.readVariableAsIntervalMs(
+ chargingStation,
+ OCPP20ComponentName.AlignedDataCtrlr,
+ OCPP20RequiredVariableName.AlignedDataInterval,
+ 900
+ )
+ }
+
public static getTxUpdatedInterval (chargingStation: ChargingStation): number {
- const variableManager = OCPP20VariableManager.getInstance()
- const results = variableManager.getVariables(chargingStation, [
- {
- component: { name: OCPP20ComponentName.SampledDataCtrlr },
- variable: { name: OCPP20RequiredVariableName.TxUpdatedInterval },
- },
- ])
- if (results.length > 0 && results[0].attributeValue != null) {
- const intervalSeconds = parseInt(results[0].attributeValue, 10)
- if (!isNaN(intervalSeconds) && intervalSeconds > 0) {
- return secondsToMilliseconds(intervalSeconds)
- }
- }
- return secondsToMilliseconds(Constants.DEFAULT_TX_UPDATED_INTERVAL)
+ return OCPP20ServiceUtils.readVariableAsIntervalMs(
+ chargingStation,
+ OCPP20ComponentName.SampledDataCtrlr,
+ OCPP20RequiredVariableName.TxUpdatedInterval,
+ Constants.DEFAULT_TX_UPDATED_INTERVAL
+ )
}
/**
connectorId: number,
evseId?: number
): Promise<OCPP20TransactionEventResponse> {
- const connectorStatus = chargingStation.getConnectorStatus(connectorId)
- if (
- (connectorStatus?.transactionStarted === true ||
- connectorStatus?.transactionPending === true) &&
- connectorStatus.transactionId != null
- ) {
- const transactionId =
- typeof connectorStatus.transactionId === 'string'
- ? connectorStatus.transactionId
- : connectorStatus.transactionId.toString()
-
- await this.sendTransactionEvent(
- chargingStation,
- OCPP20TransactionEventEnumType.Updated,
- OCPP20TriggerReasonEnumType.Deauthorized,
- connectorId,
- transactionId,
- {
- chargingState: OCPP20ChargingStateEnumType.SuspendedEVSE,
- evseId,
- }
- )
-
- const finalMeterValues = this.buildFinalMeterValues(connectorStatus)
-
- const response = await this.sendTransactionEvent(
- chargingStation,
- OCPP20TransactionEventEnumType.Ended,
- OCPP20TriggerReasonEnumType.Deauthorized,
- connectorId,
- transactionId,
- {
- evseId,
- meterValue: finalMeterValues.length > 0 ? finalMeterValues : undefined,
- stoppedReason: OCPP20ReasonEnumType.DeAuthorized,
- }
- )
+ const { connectorStatus, transactionId } = OCPP20ServiceUtils.resolveActiveTransaction(
+ chargingStation,
+ connectorId
+ )
- OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
- resetConnectorStatus(connectorStatus)
- await sendAndSetConnectorStatus(chargingStation, connectorId, ConnectorStatusEnum.Available)
+ await this.sendTransactionEvent(
+ chargingStation,
+ OCPP20TransactionEventEnumType.Updated,
+ OCPP20TriggerReasonEnumType.Deauthorized,
+ connectorId,
+ transactionId,
+ {
+ chargingState: OCPP20ChargingStateEnumType.SuspendedEVSE,
+ evseId,
+ }
+ )
- return response
- }
- throw new OCPPError(
- ErrorType.PROPERTY_CONSTRAINT_VIOLATION,
- `No active transaction on connector ${connectorId.toString()}`
+ return this.terminateTransaction(
+ chargingStation,
+ connectorId,
+ connectorStatus,
+ transactionId,
+ OCPP20TriggerReasonEnumType.Deauthorized,
+ OCPP20ReasonEnumType.DeAuthorized,
+ evseId
)
}
triggerReason: OCPP20TriggerReasonEnumType = OCPP20TriggerReasonEnumType.RemoteStop,
stoppedReason: OCPP20ReasonEnumType = OCPP20ReasonEnumType.Remote
): Promise<OCPP20TransactionEventResponse> {
- const connectorStatus = chargingStation.getConnectorStatus(connectorId)
- if (
- (connectorStatus?.transactionStarted === true ||
- connectorStatus?.transactionPending === true) &&
- connectorStatus.transactionId != null
- ) {
- let transactionId: string
- if (typeof connectorStatus.transactionId === 'string') {
- transactionId = connectorStatus.transactionId
- } else {
- transactionId = connectorStatus.transactionId.toString()
- logger.warn(
- `${chargingStation.logPrefix()} ${moduleName}.requestStopTransaction: Non-string transaction ID ${transactionId} converted to string for OCPP 2.0`
- )
- }
-
- // F03.FR.04: Build final meter values for TransactionEvent(Ended)
- const finalMeterValues = this.buildFinalMeterValues(connectorStatus)
-
- const response = await this.sendTransactionEvent(
- chargingStation,
- OCPP20TransactionEventEnumType.Ended,
- triggerReason,
- connectorId,
- transactionId,
- {
- evseId,
- meterValue: finalMeterValues.length > 0 ? finalMeterValues : undefined,
- stoppedReason,
- }
- )
-
- OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
- resetConnectorStatus(connectorStatus)
- await sendAndSetConnectorStatus(chargingStation, connectorId, ConnectorStatusEnum.Available)
+ const { connectorStatus, transactionId } = OCPP20ServiceUtils.resolveActiveTransaction(
+ chargingStation,
+ connectorId
+ )
- return response
- }
- throw new OCPPError(
- ErrorType.PROPERTY_CONSTRAINT_VIOLATION,
- `No active transaction on connector ${connectorId.toString()}`
+ return this.terminateTransaction(
+ chargingStation,
+ connectorId,
+ connectorStatus,
+ transactionId,
+ triggerReason,
+ stoppedReason,
+ evseId
)
}
}
}
- private static buildFinalMeterValues (connectorStatus: ConnectorStatus): OCPP20MeterValue[] {
- const finalMeterValues: OCPP20MeterValue[] = []
+ private static buildEnergyMeterValues (
+ connectorStatus: ConnectorStatus,
+ context: OCPP20ReadingContextEnumType
+ ): OCPP20MeterValue[] {
+ const meterValues: OCPP20MeterValue[] = []
const energyValue = connectorStatus.transactionEnergyActiveImportRegisterValue ?? 0
if (energyValue >= 0) {
- finalMeterValues.push({
+ meterValues.push({
sampledValue: [
{
- context: OCPP20ReadingContextEnumType.TRANSACTION_END,
+ context,
measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER,
value: energyValue,
},
timestamp: new Date(),
})
}
- return finalMeterValues
+ return meterValues
+ }
+
+ private static buildTransactionEndedMeterValues (
+ connectorStatus: ConnectorStatus
+ ): OCPP20MeterValue[] {
+ return OCPP20ServiceUtils.buildEnergyMeterValues(
+ connectorStatus,
+ OCPP20ReadingContextEnumType.TRANSACTION_END
+ )
+ }
+
+ private static readVariableAsIntervalMs (
+ chargingStation: ChargingStation,
+ componentName: string,
+ variableName: string,
+ defaultSeconds: number
+ ): number {
+ const variableManager = OCPP20VariableManager.getInstance()
+ const results = variableManager.getVariables(chargingStation, [
+ {
+ component: { name: componentName },
+ variable: { name: variableName },
+ },
+ ])
+ if (results.length > 0 && results[0].attributeValue != null) {
+ const intervalSeconds = parseInt(results[0].attributeValue, 10)
+ if (!isNaN(intervalSeconds) && intervalSeconds > 0) {
+ return secondsToMilliseconds(intervalSeconds)
+ }
+ }
+ return secondsToMilliseconds(defaultSeconds)
+ }
+
+ private static resolveActiveTransaction (
+ chargingStation: ChargingStation,
+ connectorId: number
+ ): { connectorStatus: ConnectorStatus; transactionId: string } {
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ if (
+ (connectorStatus?.transactionStarted === true ||
+ connectorStatus?.transactionPending === true) &&
+ connectorStatus.transactionId != null
+ ) {
+ let transactionId: string
+ if (typeof connectorStatus.transactionId === 'string') {
+ transactionId = connectorStatus.transactionId
+ } else {
+ transactionId = connectorStatus.transactionId.toString()
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.resolveActiveTransaction: Non-string transaction ID ${transactionId} converted to string for OCPP 2.0`
+ )
+ }
+ return { connectorStatus, transactionId }
+ }
+ throw new OCPPError(
+ ErrorType.PROPERTY_CONSTRAINT_VIOLATION,
+ `No active transaction on connector ${connectorId.toString()}`
+ )
+ }
+
+ private static async terminateTransaction (
+ chargingStation: ChargingStation,
+ connectorId: number,
+ connectorStatus: ConnectorStatus,
+ transactionId: string,
+ triggerReason: OCPP20TriggerReasonEnumType,
+ stoppedReason: OCPP20ReasonEnumType,
+ evseId?: number
+ ): Promise<OCPP20TransactionEventResponse> {
+ const endedMeterValues = this.buildTransactionEndedMeterValues(connectorStatus)
+
+ const response = await this.sendTransactionEvent(
+ chargingStation,
+ OCPP20TransactionEventEnumType.Ended,
+ triggerReason,
+ connectorId,
+ transactionId,
+ {
+ evseId,
+ meterValue: endedMeterValues.length > 0 ? endedMeterValues : undefined,
+ stoppedReason,
+ }
+ )
+
+ OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+ resetConnectorStatus(connectorStatus)
+ await sendAndSetConnectorStatus(chargingStation, connectorId, ConnectorStatusEnum.Available)
+
+ return response
}
}
export function buildTransactionEvent (
}
OCPP20ServiceUtils.resetTransactionSequenceNumber(chargingStation, connectorId)
}
+ const startedMeterValues =
+ connectorStatus != null
+ ? OCPP20ServiceUtils.buildTransactionStartedMeterValues(connectorStatus)
+ : []
const response = await OCPP20ServiceUtils.sendTransactionEvent(
chargingStation,
OCPP20TransactionEventEnumType.Started,
{
idToken:
idTag != null ? { idToken: idTag, type: OCPP20IdTokenEnumType.Central } : undefined,
+ ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }),
}
)
return {
}
export enum OCPP20RequiredVariableName {
+ AlignedDataInterval = 'Interval',
AuthorizeRemoteStart = 'AuthorizeRemoteStart',
BytesPerMessage = 'BytesPerMessage',
CertificateEntries = 'CertificateEntries',
import { OCPP20CertificateManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20CertificateManager.js'
import {
type CertificateHashDataType,
+ DeleteCertificateStatusEnumType,
HashAlgorithmEnumType,
InstallCertificateUseEnumType,
} from '../../../../src/types/index.js'
assert.notStrictEqual(result, undefined)
assert.notStrictEqual(result.status, undefined)
- assert.ok(['Accepted', 'Failed', 'NotFound'].includes(result.status))
+ assert.ok(
+ [
+ DeleteCertificateStatusEnumType.Accepted,
+ DeleteCertificateStatusEnumType.Failed,
+ DeleteCertificateStatusEnumType.NotFound,
+ ].includes(result.status)
+ )
})
await it('should return NotFound for non-existent certificate', async () => {
const result = await manager.deleteCertificate(TEST_STATION_HASH_ID, hashData)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result.status, 'NotFound')
+ assert.strictEqual(result.status, DeleteCertificateStatusEnumType.NotFound)
})
await it('should handle filesystem errors gracefully', async () => {
const result = await manager.deleteCertificate('invalid-station-id', hashData)
assert.notStrictEqual(result, undefined)
- assert.ok(['Failed', 'NotFound'].includes(result.status))
+ assert.ok(
+ [DeleteCertificateStatusEnumType.Failed, DeleteCertificateStatusEnumType.NotFound].includes(
+ result.status
+ )
+ )
})
})
type OCPP20CertificateSignedResponse,
OCPP20RequestCommand,
OCPPVersion,
+ ReasonCodeEnumType,
} from '../../../../src/types/index.js'
import { Constants } from '../../../../src/utils/index.js'
import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
assert.notStrictEqual(response, undefined)
assert.strictEqual(response.status, GenericStatus.Rejected)
assert.notStrictEqual(response.statusInfo, undefined)
- assert.strictEqual(response.statusInfo?.reasonCode, 'InternalError')
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.InternalError)
})
})
ChangeAvailabilityStatusEnumType,
OCPP20OperationalStatusEnumType,
OCPPVersion,
+ ReasonCodeEnumType,
} from '../../../../src/types/index.js'
import { Constants } from '../../../../src/utils/index.js'
import {
assert.strictEqual(response.status, ChangeAvailabilityStatusEnumType.Rejected)
assert.notStrictEqual(response.statusInfo, undefined)
- assert.strictEqual(response.statusInfo?.reasonCode, 'UnknownEvse')
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.UnknownEvse)
})
await it('should accept when already in requested state (idempotent)', () => {
OCPPVersion,
ReportBaseEnumType,
type ReportDataType,
+ SetVariableStatusEnumType,
StandardParametersKey,
} from '../../../../src/types/index.js'
import { Constants } from '../../../../src/utils/index.js'
variable: { name: OCPP20RequiredVariableName.TimeSource },
},
])
- assert.strictEqual(setResult[0].attributeStatus, 'Accepted')
+ assert.strictEqual(setResult[0].attributeStatus, SetVariableStatusEnumType.Accepted)
// Build report; value should be truncated to length 10
const reportData = testableService.buildReportData(station, ReportBaseEnumType.FullInventory)
OCPP20ChargingProfilePurposeEnumType,
OCPP20IdTokenEnumType,
OCPP20IncomingRequestCommand,
+ OCPP20MeasurandEnumType,
+ OCPP20ReadingContextEnumType,
OCPP20RequestCommand,
OCPP20TransactionEventEnumType,
OCPP20TriggerReasonEnumType,
]
assert.strictEqual(args[1], OCPP20RequestCommand.TRANSACTION_EVENT)
assert.strictEqual(args[2].eventType, OCPP20TransactionEventEnumType.Started)
+ assert.ok(
+ Array.isArray(args[2].meterValue) && args[2].meterValue.length > 0,
+ 'TransactionEvent(Started) should include non-empty meterValue array'
+ )
+ assert.strictEqual(
+ args[2].meterValue[0].sampledValue[0].context,
+ OCPP20ReadingContextEnumType.TRANSACTION_BEGIN
+ )
+ assert.strictEqual(
+ args[2].meterValue[0].sampledValue[0].measurand,
+ OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER
+ )
})
await it('should NOT call TransactionEvent when response is Rejected', () => {
import {
OCPP20IdTokenEnumType,
OCPP20IncomingRequestCommand,
+ OCPP20MeasurandEnumType,
+ OCPP20ReadingContextEnumType,
OCPP20ReasonEnumType,
OCPP20RequestCommand,
OCPP20TransactionEventEnumType,
const sampledValue = meterValue.sampledValue[0]
assert.strictEqual(sampledValue.value, 12345.67)
- assert.strictEqual(sampledValue.context, 'Transaction.End')
- assert.strictEqual(sampledValue.measurand, 'Energy.Active.Import.Register')
+ assert.strictEqual(sampledValue.context, OCPP20ReadingContextEnumType.TRANSACTION_END)
+ assert.strictEqual(
+ sampledValue.measurand,
+ OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER
+ )
})
})
})
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {
idTokenInfo: {
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {
idTokenInfo: {
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {
idTokenInfo: {
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {
chargingPriority: 5,
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {}
const requestPayload = buildTransactionEventRequest(TEST_TRANSACTION_ID)
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {
idTokenInfo: {
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {
idTokenInfo: {
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {
totalCost: 12.5,
const mockDeauthTransaction = mock.method(
OCPP20ServiceUtils,
'requestDeauthorizeTransaction',
- () => Promise.resolve({ status: 'Accepted' })
+ () => Promise.resolve({} as OCPP20TransactionEventResponse)
)
const payload: OCPP20TransactionEventResponse = {
idTokenInfo: {
import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js'
import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
-import { OCPP20IncomingRequestCommand, OCPP20RequestCommand } from '../../../../src/types/index.js'
+import {
+ MessageTriggerEnumType,
+ OCPP20IncomingRequestCommand,
+ OCPP20RequestCommand,
+ ResetEnumType,
+} from '../../../../src/types/index.js'
import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
const AjvConstructor = _Ajv.default
await it('should pass validation for valid Reset payloads', () => {
const validate = makeValidator('ResetRequest.json')
- assert.strictEqual(validate({ type: 'Immediate' }), true)
- assert.strictEqual(validate({ type: 'OnIdle' }), true)
- assert.strictEqual(validate({ evseId: 1, type: 'OnIdle' }), true)
+ assert.strictEqual(validate({ type: ResetEnumType.Immediate }), true)
+ assert.strictEqual(validate({ type: ResetEnumType.OnIdle }), true)
+ assert.strictEqual(validate({ evseId: 1, type: ResetEnumType.OnIdle }), true)
})
await it('should pass validation for valid TriggerMessage payloads', () => {
const validate = makeValidator('TriggerMessageRequest.json')
- assert.strictEqual(validate({ requestedMessage: 'Heartbeat' }), true)
- assert.strictEqual(validate({ requestedMessage: 'BootNotification' }), true)
+ assert.strictEqual(validate({ requestedMessage: MessageTriggerEnumType.Heartbeat }), true)
+ assert.strictEqual(
+ validate({ requestedMessage: MessageTriggerEnumType.BootNotification }),
+ true
+ )
})
await describe('schema registration coverage', async () => {
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
+import type { ConnectorStatus } from '../../../../src/types/ConnectorStatus.js'
import type { EmptyObject } from '../../../../src/types/index.js'
import {
OCPP20IdTokenEnumType,
type OCPP20IdTokenType,
OCPP20MeasurandEnumType,
+ OCPP20OperationalStatusEnumType,
OCPP20ReadingContextEnumType,
OCPP20ReasonEnumType,
+ OCPP20RequestCommand,
OCPP20RequiredVariableName,
OCPP20TransactionEventEnumType,
type OCPP20TransactionType,
// Verify the request was sent with correct trigger reason
assert.strictEqual(sentRequests.length, 1)
- assert.strictEqual(sentRequests[0].command, 'TransactionEvent')
+ assert.strictEqual(sentRequests[0].command, OCPP20RequestCommand.TRANSACTION_EVENT)
assert.strictEqual(
sentRequests[0].payload.eventType,
OCPP20TransactionEventEnumType.Updated
await OCPP20ServiceUtils.requestDeauthorizeTransaction(mockTracking.station, connectorId, 1)
// Assert
- const txEvents = mockTracking.sentRequests.filter(r => r.command === 'TransactionEvent')
+ const txEvents = mockTracking.sentRequests.filter(
+ r => r.command === (OCPP20RequestCommand.TRANSACTION_EVENT as string)
+ )
assert.strictEqual(txEvents.length, 2)
const updatedEvent = txEvents[0].payload
await OCPP20ServiceUtils.requestDeauthorizeTransaction(mockTracking.station, connectorId, 2)
// Assert
- const txEvents = mockTracking.sentRequests.filter(r => r.command === 'TransactionEvent')
+ const txEvents = mockTracking.sentRequests.filter(
+ r => r.command === (OCPP20RequestCommand.TRANSACTION_EVENT as string)
+ )
assert.strictEqual(txEvents.length, 2)
const endedPayload = txEvents[1].payload
await OCPP20ServiceUtils.requestStopTransaction(mockTracking.station, connectorId, 1)
// Assert
- const txEvents = mockTracking.sentRequests.filter(r => r.command === 'TransactionEvent')
+ const txEvents = mockTracking.sentRequests.filter(
+ r => r.command === (OCPP20RequestCommand.TRANSACTION_EVENT as string)
+ )
assert.strictEqual(txEvents.length, 1)
const endedEvent = txEvents[0].payload
)
// Assert
- const txEvents = mockTracking.sentRequests.filter(r => r.command === 'TransactionEvent')
+ const txEvents = mockTracking.sentRequests.filter(
+ r => r.command === (OCPP20RequestCommand.TRANSACTION_EVENT as string)
+ )
assert.strictEqual(txEvents.length, 1)
const endedEvent = txEvents[0].payload
assert.strictEqual(endedEvent.stoppedReason, customStoppedReason)
})
})
+
+ await describe('buildTransactionStartedMeterValues', async () => {
+ await it('should build meter value with Transaction.Begin context and energy register', () => {
+ const connectorStatus = {
+ availability: OCPP20OperationalStatusEnumType.Operative,
+ MeterValues: [],
+ transactionEnergyActiveImportRegisterValue: 1234,
+ } as unknown as ConnectorStatus
+
+ const result = OCPP20ServiceUtils.buildTransactionStartedMeterValues(connectorStatus)
+
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].sampledValue.length, 1)
+ assert.strictEqual(
+ result[0].sampledValue[0].context,
+ OCPP20ReadingContextEnumType.TRANSACTION_BEGIN
+ )
+ assert.strictEqual(
+ result[0].sampledValue[0].measurand,
+ OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER
+ )
+ assert.strictEqual(result[0].sampledValue[0].value, 1234)
+ assert.ok(result[0].timestamp instanceof Date)
+ })
+
+ await it('should include meter value with 0 energy when register value is undefined (zero is a valid begin reading)', () => {
+ const connectorStatus = {
+ availability: OCPP20OperationalStatusEnumType.Operative,
+ MeterValues: [],
+ } as unknown as ConnectorStatus
+
+ const result = OCPP20ServiceUtils.buildTransactionStartedMeterValues(connectorStatus)
+
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].sampledValue[0].value, 0)
+ })
+
+ await it('should return empty array when energy register value is negative', () => {
+ const connectorStatus = {
+ availability: OCPP20OperationalStatusEnumType.Operative,
+ MeterValues: [],
+ transactionEnergyActiveImportRegisterValue: -1,
+ } as unknown as ConnectorStatus
+
+ const result = OCPP20ServiceUtils.buildTransactionStartedMeterValues(connectorStatus)
+
+ assert.strictEqual(result.length, 0)
+ })
+ })
})
import { afterEach, describe, it } from 'node:test'
import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
+import { ReasonCodeEnumType } from '../../../../src/types/index.js'
import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
interface MockLogger {
assert.strictEqual(result.rejected, true)
assert.strictEqual(result.results.length, 3)
for (const r of result.results as RejectedResult[]) {
- assert.strictEqual(r.reasonCode, 'TooManyElements')
+ assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooManyElements)
assert.ok(r.info.includes('ItemsPerMessage limit 2'))
}
})
assert.strictEqual(result.rejected, true)
assert.strictEqual(result.results.length, 2)
for (const r of result.results as RejectedResult[]) {
- assert.strictEqual(r.reasonCode, 'TooManyElements')
+ assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooManyElements)
}
})
assert.strictEqual(result.rejected, true)
assert.strictEqual(result.results.length, 1)
const r = (result.results as RejectedResult[])[0]
- assert.strictEqual(r.reasonCode, 'TooLargeElement')
+ assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooLargeElement)
assert.ok(r.info.includes('BytesPerMessage limit 1'))
})
assert.strictEqual(result.rejected, true)
assert.strictEqual(result.results.length, 2)
for (const r of result.results as RejectedResult[]) {
- assert.strictEqual(r.reasonCode, 'TooLargeElement')
+ assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooLargeElement)
}
})
assert.strictEqual(result.rejected, true)
for (const r of result.results as RejectedResult[]) {
- assert.strictEqual(r.reasonCode, 'TooManyElements')
+ assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooManyElements)
}
})
})
)
assert.strictEqual(capturedReasons.length, 1)
- assert.strictEqual(capturedReasons[0].reasonCode, 'TooLargeElement')
+ assert.strictEqual(capturedReasons[0].reasonCode, ReasonCodeEnumType.TooLargeElement)
assert.strictEqual(typeof capturedReasons[0].info, 'string')
assert.ok(capturedReasons[0].info.length > 0)
})
import type { ChargingStation } from '../../src/charging-station/index.js'
import type { Statistics, TimestampedData } from '../../src/types/index.js'
-import { ChargingStationWorkerMessageEvents } from '../../src/types/index.js'
+import { AvailabilityType, ChargingStationWorkerMessageEvents } from '../../src/types/index.js'
import {
buildAddedMessage,
buildDeletedMessage,
[
0,
{
- availability: 'Operative',
+ availability: AvailabilityType.Operative,
MeterValues: [],
},
],
[
1,
{
- availability: 'Operative',
+ availability: AvailabilityType.Operative,
MeterValues: [],
},
],