delete connectorStatus.transactionGroupIdToken
connectorStatus.transactionEnergyActiveImportRegisterValue = 0
delete connectorStatus.transactionBeginMeterValue
+ delete connectorStatus.transactionEndedMeterValues
+ if (connectorStatus.transactionEndedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionEndedMeterValuesSetInterval)
+ delete connectorStatus.transactionEndedMeterValuesSetInterval
+ }
delete connectorStatus.transactionSeqNo
delete connectorStatus.transactionEvseSent
delete connectorStatus.transactionIdTokenSent
connectorId++
) {
if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
- OCPP16ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
- OCPP16ServiceUtils.startPeriodicMeterValues(
+ OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
+ OCPP16ServiceUtils.startUpdatedMeterValues(
chargingStation,
connectorId,
secondsToMilliseconds(convertToInt(value))
chargingStation,
OCPP16StandardParametersKey.MeterValueSampleInterval
)
- OCPP16ServiceUtils.startPeriodicMeterValues(
+ OCPP16ServiceUtils.startUpdatedMeterValues(
chargingStation,
connectorId,
configuredMeterValueSampleInterval != null
) {
transactionConnectorStatus.locked = false
}
- OCPP16ServiceUtils.stopPeriodicMeterValues(chargingStation, transactionConnectorId)
+ OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, transactionConnectorId)
const logMsg = `${chargingStation.logPrefix()} ${moduleName}.handleResponseStopTransaction: Transaction with id ${requestPayload.transactionId.toString()} STOPPED on ${
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
chargingStation.stationInfo?.chargingStationId
chargingStation: ChargingStation,
connectorId: number
): Promise<void> {
- OCPP16ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+ OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
resetConnectorStatus(connectorStatus)
await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
!cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
}
- public static startPeriodicMeterValues (
+ public static async startTransactionOnConnector (
+ chargingStation: ChargingStation,
+ connectorId: number,
+ idTag?: string
+ ): Promise<StartTransactionResponse> {
+ return chargingStation.ocppRequestService.requestHandler<
+ Partial<StartTransactionRequest>,
+ StartTransactionResponse
+ >(chargingStation, RequestCommand.START_TRANSACTION, {
+ connectorId,
+ ...(idTag != null && { idTag }),
+ })
+ }
+
+ public static startUpdatedMeterValues (
chargingStation: ChargingStation,
connectorId: number,
interval: number
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
if (connectorStatus == null) {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Connector ${connectorId.toString()} not found`
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Connector ${connectorId.toString()} not found`
)
return
}
if (connectorStatus.transactionStarted !== true || connectorStatus.transactionId == null) {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: No active transaction on connector ${connectorId.toString()}`
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: No active transaction on connector ${connectorId.toString()}`
)
return
}
if (interval <= 0) {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: MeterValueSampleInterval set to ${interval.toString()}, not sending MeterValues`
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: MeterValueSampleInterval set to ${interval.toString()}, not sending MeterValues`
)
return
}
- connectorStatus.transactionMeterValuesSetInterval = setInterval(() => {
+ connectorStatus.transactionUpdatedMeterValuesSetInterval = setInterval(() => {
const transactionId = convertToInt(connectorStatus.transactionId)
const meterValue = buildMeterValue(chargingStation, transactionId, interval)
chargingStation.ocppRequestService
)
.catch((error: unknown) => {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Error while sending '${RequestCommand.METER_VALUES}':`,
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Error while sending '${RequestCommand.METER_VALUES}':`,
error
)
})
}, clampToSafeTimerValue(interval))
}
- public static async startTransactionOnConnector (
- chargingStation: ChargingStation,
- connectorId: number,
- idTag?: string
- ): Promise<StartTransactionResponse> {
- return chargingStation.ocppRequestService.requestHandler<
- Partial<StartTransactionRequest>,
- StartTransactionResponse
- >(chargingStation, RequestCommand.START_TRANSACTION, {
- connectorId,
- ...(idTag != null && { idTag }),
- })
- }
-
- public static stopPeriodicMeterValues (
- chargingStation: ChargingStation,
- connectorId: number
- ): void {
- const connectorStatus = chargingStation.getConnectorStatus(connectorId)
- if (connectorStatus?.transactionMeterValuesSetInterval != null) {
- clearInterval(connectorStatus.transactionMeterValuesSetInterval)
- delete connectorStatus.transactionMeterValuesSetInterval
- }
- }
-
public static async stopTransactionOnConnector (
chargingStation: ChargingStation,
connectorId: number,
})
}
+ public static stopUpdatedMeterValues (
+ chargingStation: ChargingStation,
+ connectorId: number
+ ): void {
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ if (connectorStatus?.transactionUpdatedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+ delete connectorStatus.transactionUpdatedMeterValuesSetInterval
+ }
+ }
+
private static readonly composeChargingSchedule = (
chargingSchedule: OCPP16ChargingSchedule,
compositeInterval: Interval
connectorId: number,
evseId?: number
): Promise<void> {
- OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+ OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
resetConnectorStatus(connectorStatus)
await restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
)
})
const txUpdatedInterval = OCPP20ServiceUtils.getTxUpdatedInterval(chargingStation)
- OCPP20ServiceUtils.startPeriodicMeterValues(
+ OCPP20ServiceUtils.startUpdatedMeterValues(
chargingStation,
connectorId,
txUpdatedInterval
)
+ const txEndedInterval = OCPP20ServiceUtils.getTxEndedInterval(chargingStation)
+ OCPP20ServiceUtils.startEndedMeterValues(chargingStation, connectorId, txEndedInterval)
}
logger.info(
`${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: Transaction ${requestPayload.transactionInfo.transactionId} STARTED on connector ${String(connectorId)}`
OCPP20ComponentName.SampledDataCtrlr,
OCPP20RequiredVariableName.TxStartedMeasurands
)
- const meterValue = buildMeterValue(
+ const startedMeterValue = buildMeterValue(
chargingStation,
transactionId,
0,
measurandsKey,
OCPP20ReadingContextEnumType.TRANSACTION_BEGIN
) as OCPP20MeterValue
- return meterValue.sampledValue.length > 0 ? [meterValue] : []
+ return startedMeterValue.sampledValue.length > 0 ? [startedMeterValue] : []
} catch (error) {
logger.warn(
`${chargingStation.logPrefix()} ${moduleName}.buildTransactionStartedMeterValues: ${(error as Error).message}`
connectorId: number,
connectorStatus: ConnectorStatus
): Promise<void> {
- OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+ OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
resetConnectorStatus(connectorStatus)
connectorStatus.locked = false
await sendAndSetConnectorStatus(chargingStation, {
)
}
+ public static getTxEndedInterval (chargingStation: ChargingStation): number {
+ return OCPP20ServiceUtils.readVariableAsIntervalMs(
+ chargingStation,
+ OCPP20ComponentName.SampledDataCtrlr,
+ OCPP20RequiredVariableName.TxEndedInterval,
+ 0
+ )
+ }
+
public static getTxUpdatedInterval (chargingStation: ChargingStation): number {
return OCPP20ServiceUtils.readVariableAsIntervalMs(
chargingStation,
}
}
- public static startPeriodicMeterValues (
+ public static startEndedMeterValues (
+ chargingStation: ChargingStation,
+ connectorId: number,
+ interval: number
+ ): void {
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ if (connectorStatus == null) {
+ return
+ }
+ connectorStatus.transactionEndedMeterValues = []
+ if (interval <= 0) {
+ return
+ }
+ if (connectorStatus.transactionEndedMeterValuesSetInterval != null) {
+ OCPP20ServiceUtils.stopEndedMeterValues(chargingStation, connectorId)
+ }
+ connectorStatus.transactionEndedMeterValuesSetInterval = setInterval(() => {
+ const cs = chargingStation.getConnectorStatus(connectorId)
+ if (cs?.transactionStarted === true && cs.transactionId != null) {
+ const measurandsKey = buildConfigKey(
+ OCPP20ComponentName.SampledDataCtrlr,
+ OCPP20RequiredVariableName.TxEndedMeasurands
+ )
+ const meterValue = buildMeterValue(
+ chargingStation,
+ cs.transactionId,
+ interval,
+ measurandsKey
+ ) as OCPP20MeterValue
+ if (meterValue.sampledValue.length > 0) {
+ cs.transactionEndedMeterValues?.push(meterValue)
+ }
+ }
+ }, clampToSafeTimerValue(interval))
+ logger.info(
+ `${chargingStation.logPrefix()} ${moduleName}.startEndedMeterValues: TxEndedInterval started every ${formatDurationMilliSeconds(interval)}`
+ )
+ }
+
+ public static startUpdatedMeterValues (
chargingStation: ChargingStation,
connectorId: number,
interval: number
const initialConnectorStatus = chargingStation.getConnectorStatus(connectorId)
if (initialConnectorStatus == null) {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Connector ${connectorId.toString()} not found`
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Connector ${connectorId.toString()} not found`
)
return
}
if (interval <= 0) {
logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: TxUpdatedInterval is ${interval.toString()}, not starting periodic TransactionEvent`
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: TxUpdatedInterval is ${interval.toString()}, not starting periodic TransactionEvent`
)
return
}
- if (initialConnectorStatus.transactionMeterValuesSetInterval != null) {
+ if (initialConnectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
logger.warn(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: TxUpdatedInterval already started, stopping first`
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: TxUpdatedInterval already started, stopping first`
)
- OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+ OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
}
- initialConnectorStatus.transactionMeterValuesSetInterval = setInterval(() => {
+ initialConnectorStatus.transactionUpdatedMeterValuesSetInterval = setInterval(() => {
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
if (connectorStatus?.transactionStarted === true && connectorStatus.transactionId != null) {
if (
evseId
).catch((error: unknown) => {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Error terminating deauthorized transaction:`,
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Error terminating deauthorized transaction:`,
error
)
})
{ meterValue: [meterValue] }
).catch((error: unknown) => {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Error sending periodic TransactionEvent:`,
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Error sending periodic TransactionEvent:`,
error
)
})
}
}, clampToSafeTimerValue(interval))
logger.info(
- `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: TxUpdatedInterval started every ${formatDurationMilliSeconds(interval)}`
+ `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: TxUpdatedInterval started every ${formatDurationMilliSeconds(interval)}`
)
}
}
}
- public static stopPeriodicMeterValues (
+ public static stopEndedMeterValues (chargingStation: ChargingStation, connectorId: number): void {
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ if (connectorStatus?.transactionEndedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionEndedMeterValuesSetInterval)
+ delete connectorStatus.transactionEndedMeterValuesSetInterval
+ logger.info(
+ `${chargingStation.logPrefix()} ${moduleName}.stopEndedMeterValues: TxEndedInterval stopped`
+ )
+ }
+ }
+
+ public static stopUpdatedMeterValues (
chargingStation: ChargingStation,
connectorId: number
): void {
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
- if (connectorStatus?.transactionMeterValuesSetInterval != null) {
- clearInterval(connectorStatus.transactionMeterValuesSetInterval)
- delete connectorStatus.transactionMeterValuesSetInterval
+ if (connectorStatus?.transactionUpdatedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+ delete connectorStatus.transactionUpdatedMeterValuesSetInterval
logger.info(
- `${chargingStation.logPrefix()} ${moduleName}.stopPeriodicMeterValues: TxUpdatedInterval stopped`
+ `${chargingStation.logPrefix()} ${moduleName}.stopUpdatedMeterValues: TxUpdatedInterval stopped`
)
}
}
private static buildTransactionEndedMeterValues (
chargingStation: ChargingStation,
+ connectorId: number,
transactionId: number | string
): OCPP20MeterValue[] {
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ const endedMeterValues = (connectorStatus?.transactionEndedMeterValues ??
+ []) as OCPP20MeterValue[]
+
try {
const measurandsKey = buildConfigKey(
OCPP20ComponentName.SampledDataCtrlr,
OCPP20RequiredVariableName.TxEndedMeasurands
)
- const meterValue = buildMeterValue(
+ const finalMeterValue = buildMeterValue(
chargingStation,
transactionId,
0,
measurandsKey,
OCPP20ReadingContextEnumType.TRANSACTION_END
) as OCPP20MeterValue
- return meterValue.sampledValue.length > 0 ? [meterValue] : []
+ if (finalMeterValue.sampledValue.length > 0) {
+ return [...endedMeterValues, finalMeterValue]
+ }
} catch (error) {
logger.warn(
`${chargingStation.logPrefix()} ${moduleName}.buildTransactionEndedMeterValues: ${(error as Error).message}`
)
- return []
}
+ return endedMeterValues.length > 0 ? endedMeterValues : []
}
private static readVariableAsBoolean (
stoppedReason: OCPP20ReasonEnumType,
evseId?: number
): Promise<OCPP20TransactionEventResponse> {
- const endedMeterValues = this.buildTransactionEndedMeterValues(chargingStation, transactionId)
+ this.stopEndedMeterValues(chargingStation, connectorId)
+ const endedMeterValues = this.buildTransactionEndedMeterValues(
+ chargingStation,
+ connectorId,
+ transactionId
+ )
const response = await this.sendTransactionEvent(
chargingStation,
supportedAttributes: [AttributeEnumType.Actual],
variable: 'SignReadings',
},
- [buildRegistryKey(OCPP20ComponentName.AlignedDataCtrlr, 'TxEndedInterval')]: {
- component: OCPP20ComponentName.AlignedDataCtrlr,
- dataType: DataEnumType.integer,
- defaultValue: '900',
- description:
- 'Size (in seconds) of the clock-aligned data interval, intended to be transmitted in the TransactionEventRequest (eventType = Ended) message.',
- min: 1,
- mutability: MutabilityEnumType.ReadWrite,
- persistence: PersistenceEnumType.Persistent,
- required: true,
- supportedAttributes: [AttributeEnumType.Actual],
- unit: OCPP20UnitEnumType.SECONDS,
- variable: 'TxEndedInterval',
- },
[buildRegistryKey(
OCPP20ComponentName.AlignedDataCtrlr,
OCPP20RequiredVariableName.AlignedDataInterval
supportedAttributes: [AttributeEnumType.Actual],
variable: OCPP20RequiredVariableName.Measurands,
},
+ [buildRegistryKey(
+ OCPP20ComponentName.AlignedDataCtrlr,
+ OCPP20RequiredVariableName.TxEndedInterval
+ )]: {
+ component: OCPP20ComponentName.AlignedDataCtrlr,
+ dataType: DataEnumType.integer,
+ defaultValue: '900',
+ description:
+ 'Size (in seconds) of the clock-aligned data interval, intended to be transmitted in the TransactionEventRequest (eventType = Ended) message.',
+ min: 1,
+ mutability: MutabilityEnumType.ReadWrite,
+ persistence: PersistenceEnumType.Persistent,
+ required: true,
+ supportedAttributes: [AttributeEnumType.Actual],
+ unit: OCPP20UnitEnumType.SECONDS,
+ variable: OCPP20RequiredVariableName.TxEndedInterval,
+ },
[buildRegistryKey(
OCPP20ComponentName.AlignedDataCtrlr,
OCPP20RequiredVariableName.TxEndedMeasurands
supportedAttributes: [AttributeEnumType.Actual],
variable: 'SignReadings',
},
- [buildRegistryKey(OCPP20ComponentName.SampledDataCtrlr, 'TxEndedInterval')]: {
- component: OCPP20ComponentName.SampledDataCtrlr,
- dataType: DataEnumType.integer,
- defaultValue: '60',
- description:
- 'Interval between sampling of metering data, intended to be transmitted in the TransactionEventRequest (eventType = Ended) message.',
- min: 1,
- mutability: MutabilityEnumType.ReadWrite,
- persistence: PersistenceEnumType.Persistent,
- required: true,
- supportedAttributes: [AttributeEnumType.Actual],
- unit: OCPP20UnitEnumType.SECONDS,
- variable: 'TxEndedInterval',
- },
[buildRegistryKey(OCPP20ComponentName.SampledDataCtrlr, OCPP20MeasurandEnumType.CURRENT_IMPORT)]:
{
component: OCPP20ComponentName.SampledDataCtrlr,
supportedAttributes: [AttributeEnumType.Actual],
variable: OCPP20RequiredVariableName.Enabled,
},
+ [buildRegistryKey(
+ OCPP20ComponentName.SampledDataCtrlr,
+ OCPP20RequiredVariableName.TxEndedInterval
+ )]: {
+ component: OCPP20ComponentName.SampledDataCtrlr,
+ dataType: DataEnumType.integer,
+ defaultValue: '60',
+ description:
+ 'Interval between sampling of metering data, intended to be transmitted in the TransactionEventRequest (eventType = Ended) message.',
+ min: 1,
+ mutability: MutabilityEnumType.ReadWrite,
+ persistence: PersistenceEnumType.Persistent,
+ required: true,
+ supportedAttributes: [AttributeEnumType.Actual],
+ unit: OCPP20UnitEnumType.SECONDS,
+ variable: OCPP20RequiredVariableName.TxEndedInterval,
+ },
[buildRegistryKey(
OCPP20ComponentName.SampledDataCtrlr,
OCPP20RequiredVariableName.TxEndedMeasurands
}
}
-export const startPeriodicMeterValues = async (
+export const startUpdatedMeterValues = async (
chargingStation: ChargingStation,
connectorId: number,
interval: number
switch (chargingStation.stationInfo?.ocppVersion) {
case OCPPVersion.VERSION_16: {
const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
- OCPP16ServiceUtils.startPeriodicMeterValues(chargingStation, connectorId, interval)
+ OCPP16ServiceUtils.startUpdatedMeterValues(chargingStation, connectorId, interval)
break
}
case OCPPVersion.VERSION_20:
case OCPPVersion.VERSION_201: {
const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
- OCPP20ServiceUtils.startPeriodicMeterValues(chargingStation, connectorId, interval)
+ OCPP20ServiceUtils.startUpdatedMeterValues(chargingStation, connectorId, interval)
break
}
default:
logger.error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
- `${chargingStation.logPrefix()} OCPPServiceUtils.startPeriodicMeterValues: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
+ `${chargingStation.logPrefix()} OCPPServiceUtils.startUpdatedMeterValues: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
)
}
}
-export const stopPeriodicMeterValues = async (
+export const stopUpdatedMeterValues = async (
chargingStation: ChargingStation,
connectorId: number
): Promise<void> => {
switch (chargingStation.stationInfo?.ocppVersion) {
case OCPPVersion.VERSION_16: {
const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
- OCPP16ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+ OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
break
}
case OCPPVersion.VERSION_20:
case OCPPVersion.VERSION_201: {
const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
- OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+ OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
break
}
default:
transactionBeginMeterValue?: MeterValue
transactionDeauthorized?: boolean
transactionDeauthorizedEnergyWh?: number
+ transactionEndedMeterValues?: MeterValue[]
+ transactionEndedMeterValuesSetInterval?: NodeJS.Timeout
transactionEnergyActiveImportRegisterValue?: number // In Wh
- /**
- * OCPP 2.0.1 offline-first: Queue of TransactionEvents waiting to be sent
- * Events are queued when station is offline (websocket disconnected)
- * and replayed in order when reconnected, with seqNo preserved
- */
transactionEventQueue?: QueuedTransactionEvent[]
- /**
- * OCPP 2.0.1 E01.FR.16 compliance: Track if evse has been sent for current transaction.
- * The evse field should only be provided in the first TransactionEventRequest
- * that occurs after the EV has connected.
- */
transactionEvseSent?: boolean
transactionGroupIdToken?: string
transactionId?: number | string
transactionIdTag?: string
- /**
- * OCPP 2.0.1 E03.FR.01 compliance: Track if idToken has been sent for current transaction.
- * The idToken field should be provided once in the first TransactionEventRequest
- * that occurs after the transaction has been authorized.
- */
transactionIdTokenSent?: boolean
- transactionMeterValuesSetInterval?: NodeJS.Timeout
- /**
- * OCPP 2.0.1 E02 compliance: Transaction pending CSMS acknowledgment.
- * Blocks duplicate RequestStartTransaction until response handler sets transactionStarted.
- */
transactionPending?: boolean
transactionRemoteStarted?: boolean
transactionSeqNo?: number
transactionStart?: Date
transactionStarted?: boolean
+ transactionUpdatedMeterValuesSetInterval?: NodeJS.Timeout
type?: ConnectorEnumType
}
StopTxOnEVSideDisconnect = 'StopTxOnEVSideDisconnect',
StopTxOnInvalidId = 'StopTxOnInvalidId',
TimeSource = 'TimeSource',
+ TxEndedInterval = 'TxEndedInterval',
TxEndedMeasurands = 'TxEndedMeasurands',
TxStartedMeasurands = 'TxStartedMeasurands',
TxStartPoint = 'TxStartPoint',
return [...chargingStation.connectors.entries()].map(
([
connectorId,
- { transactionEventQueue, transactionMeterValuesSetInterval, ...connector },
+ {
+ transactionEndedMeterValues,
+ transactionEndedMeterValuesSetInterval,
+ transactionEventQueue,
+ transactionUpdatedMeterValuesSetInterval,
+ ...connector
+ },
]) => ({
connector,
connectorId,
return [...chargingStation.connectors.entries()].map(
([
connectorId,
- { transactionEventQueue, transactionMeterValuesSetInterval, ...connectorStatus },
+ {
+ transactionEndedMeterValues,
+ transactionEndedMeterValuesSetInterval,
+ transactionEventQueue,
+ transactionUpdatedMeterValuesSetInterval,
+ ...connectorStatus
+ },
]) => [connectorId, connectorStatus]
)
}
connectors: [...evseStatus.connectors.entries()].map(
([
connectorId,
- { transactionEventQueue, transactionMeterValuesSetInterval, ...connector },
+ {
+ transactionEndedMeterValues,
+ transactionEndedMeterValuesSetInterval,
+ transactionEventQueue,
+ transactionUpdatedMeterValuesSetInterval,
+ ...connector
+ },
]) => ({
connector,
connectorId,
const connectorsStatus: [number, ConnectorStatus][] = [...evseStatus.connectors.entries()].map(
([
connectorId,
- { transactionEventQueue, transactionMeterValuesSetInterval, ...connector },
+ {
+ transactionEndedMeterValues,
+ transactionEndedMeterValuesSetInterval,
+ transactionEventQueue,
+ transactionUpdatedMeterValuesSetInterval,
+ ...connector
+ },
]) => [connectorId, connector]
)
const { connectors: _, ...evseStatusRest } = evseStatus
}
// Act
- OCPP16ServiceUtils.startPeriodicMeterValues(station, 1, 10000)
+ OCPP16ServiceUtils.startUpdatedMeterValues(station, 1, 10000)
// Assert - meter values interval should be created
if (connector1 != null) {
- assert.notStrictEqual(connector1.transactionMeterValuesSetInterval, undefined)
- assert.strictEqual(typeof connector1.transactionMeterValuesSetInterval, 'object')
+ assert.notStrictEqual(connector1.transactionUpdatedMeterValuesSetInterval, undefined)
+ assert.strictEqual(typeof connector1.transactionUpdatedMeterValuesSetInterval, 'object')
}
})
})
connector1.transactionStarted = true
connector1.transactionId = 100
}
- OCPP16ServiceUtils.startPeriodicMeterValues(station, 1, 10000)
- const firstInterval = connector1?.transactionMeterValuesSetInterval
+ OCPP16ServiceUtils.startUpdatedMeterValues(station, 1, 10000)
+ const firstInterval = connector1?.transactionUpdatedMeterValuesSetInterval
// Act
- OCPP16ServiceUtils.stopPeriodicMeterValues(station, 1)
- OCPP16ServiceUtils.startPeriodicMeterValues(station, 1, 15000)
- const secondInterval = connector1?.transactionMeterValuesSetInterval
+ OCPP16ServiceUtils.stopUpdatedMeterValues(station, 1)
+ OCPP16ServiceUtils.startUpdatedMeterValues(station, 1, 15000)
+ const secondInterval = connector1?.transactionUpdatedMeterValuesSetInterval
// Assert - interval should be different
assert.notStrictEqual(secondInterval, undefined)
connector1.transactionStarted = true
connector1.transactionId = 100
}
- OCPP16ServiceUtils.startPeriodicMeterValues(station, 1, 10000)
+ OCPP16ServiceUtils.startUpdatedMeterValues(station, 1, 10000)
// Act
- OCPP16ServiceUtils.stopPeriodicMeterValues(station, 1)
+ OCPP16ServiceUtils.stopUpdatedMeterValues(station, 1)
// Assert - interval should be cleared
- assert.strictEqual(connector1?.transactionMeterValuesSetInterval, undefined)
+ assert.strictEqual(connector1?.transactionUpdatedMeterValuesSetInterval, undefined)
})
})
}
// Act
- OCPP20ServiceUtils.startPeriodicMeterValues(station, 1, 5000)
+ OCPP20ServiceUtils.startUpdatedMeterValues(station, 1, 5000)
// Assert - transaction updated interval should be created
if (connector1 != null) {
- assert.notStrictEqual(connector1.transactionMeterValuesSetInterval, undefined)
- assert.strictEqual(typeof connector1.transactionMeterValuesSetInterval, 'object')
+ assert.notStrictEqual(connector1.transactionUpdatedMeterValuesSetInterval, undefined)
+ assert.strictEqual(typeof connector1.transactionUpdatedMeterValuesSetInterval, 'object')
}
})
})
connector1.transactionStarted = true
connector1.transactionId = 100
}
- OCPP20ServiceUtils.startPeriodicMeterValues(station, 1, 5000)
+ OCPP20ServiceUtils.startUpdatedMeterValues(station, 1, 5000)
// Act
- OCPP20ServiceUtils.stopPeriodicMeterValues(station, 1)
+ OCPP20ServiceUtils.stopUpdatedMeterValues(station, 1)
// Assert - interval should be cleared
- assert.strictEqual(connector1?.transactionMeterValuesSetInterval, undefined)
+ assert.strictEqual(connector1?.transactionUpdatedMeterValuesSetInterval, undefined)
})
})
})
// Clear connector transaction state and timers
for (const connectorStatus of station.connectors.values()) {
- if (connectorStatus.transactionMeterValuesSetInterval != null) {
- clearInterval(connectorStatus.transactionMeterValuesSetInterval)
- connectorStatus.transactionMeterValuesSetInterval = undefined
+ if (connectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+ connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
+ }
+ if (connectorStatus.transactionEndedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionEndedMeterValuesSetInterval)
+ connectorStatus.transactionEndedMeterValuesSetInterval = undefined
}
}
// Clear EVSE connector transaction state and timers
for (const evseStatus of station.evses.values()) {
for (const connectorStatus of evseStatus.connectors.values()) {
- if (connectorStatus.transactionMeterValuesSetInterval != null) {
- clearInterval(connectorStatus.transactionMeterValuesSetInterval)
- connectorStatus.transactionMeterValuesSetInterval = undefined
+ if (connectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+ connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
+ }
+ if (connectorStatus.transactionEndedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionEndedMeterValuesSetInterval)
+ connectorStatus.transactionEndedMeterValuesSetInterval = undefined
}
}
}
status.energyActiveImportRegisterValue = 0
status.transactionEnergyActiveImportRegisterValue = 0
- // Clear transaction interval
- if (status.transactionMeterValuesSetInterval != null) {
- clearInterval(status.transactionMeterValuesSetInterval)
- status.transactionMeterValuesSetInterval = undefined
+ if (status.transactionUpdatedMeterValuesSetInterval != null) {
+ clearInterval(status.transactionUpdatedMeterValuesSetInterval)
+ status.transactionUpdatedMeterValuesSetInterval = undefined
+ }
+
+ status.transactionEndedMeterValues = undefined
+ if (status.transactionEndedMeterValuesSetInterval != null) {
+ clearInterval(status.transactionEndedMeterValuesSetInterval)
+ status.transactionEndedMeterValuesSetInterval = undefined
}
}
// Mock meter value start/stop to avoid real timer setup
mock.method(
OCPP16ServiceUtils,
- 'startPeriodicMeterValues',
+ 'startUpdatedMeterValues',
(_station: unknown, _connectorId: number, _interval: number) => {
/* noop */
}
)
mock.method(
OCPP16ServiceUtils,
- 'stopPeriodicMeterValues',
+ 'stopUpdatedMeterValues',
(_station: unknown, _connectorId: number) => {
/* noop */
}
setMockRequestHandler(station, async () => Promise.resolve({}))
// Mock startMeterValues/stopMeterValues to avoid real timer setup
- mock.method(OCPP16ServiceUtils, 'startPeriodicMeterValues', () => {
+ mock.method(OCPP16ServiceUtils, 'startUpdatedMeterValues', () => {
/* noop */
})
- mock.method(OCPP16ServiceUtils, 'stopPeriodicMeterValues', () => {
+ mock.method(OCPP16ServiceUtils, 'stopUpdatedMeterValues', () => {
/* noop */
})
OCPP20ServiceUtils,
} from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
-import { startPeriodicMeterValues } from '../../../../src/charging-station/ocpp/OCPPServiceUtils.js'
+import { startUpdatedMeterValues } from '../../../../src/charging-station/ocpp/OCPPServiceUtils.js'
import { OCPPError } from '../../../../src/exception/index.js'
import {
AttributeEnumType,
// Clean up any running timers
for (let connectorId = 1; connectorId <= 3; connectorId++) {
const connectorStatus = mockStation.getConnectorStatus(connectorId)
- if (connectorStatus?.transactionMeterValuesSetInterval != null) {
- clearInterval(connectorStatus.transactionMeterValuesSetInterval)
- connectorStatus.transactionMeterValuesSetInterval = undefined
+ if (connectorStatus?.transactionUpdatedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+ connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
}
}
standardCleanup()
})
- await describe('startPeriodicMeterValues', async () => {
+ await describe('startUpdatedMeterValues', async () => {
await it('should not start OCPP 2.0 timer for OCPP 1.6 stations via unified dispatch', async t => {
await withMockTimers(t, ['setInterval'], async () => {
const { station: ocpp16Station } = createMockChargingStation({
},
})
- await startPeriodicMeterValues(ocpp16Station, 1, 60000)
+ await startUpdatedMeterValues(ocpp16Station, 1, 60000)
const connectorStatus = ocpp16Station.getConnectorStatus(1)
- assert.strictEqual(connectorStatus?.transactionMeterValuesSetInterval, undefined)
+ assert.strictEqual(connectorStatus?.transactionUpdatedMeterValuesSetInterval, undefined)
})
})
// Zero interval should not start timer
// This is verified by the implementation logging debug message
- assert.strictEqual(connectorStatus.transactionMeterValuesSetInterval, undefined)
+ assert.strictEqual(connectorStatus.transactionUpdatedMeterValuesSetInterval, undefined)
})
await it('should not start timer when interval is negative', () => {
assert(connectorStatus != null)
// Negative interval should not start timer
- assert.strictEqual(connectorStatus.transactionMeterValuesSetInterval, undefined)
+ assert.strictEqual(connectorStatus.transactionUpdatedMeterValuesSetInterval, undefined)
})
await it('should handle non-existent connector gracefully', () => {
connectorStatus.idTagLocalAuthorized = false
// Clear any transaction interval
- if (connectorStatus.transactionMeterValuesSetInterval != null) {
- clearInterval(connectorStatus.transactionMeterValuesSetInterval)
- connectorStatus.transactionMeterValuesSetInterval = undefined
+ if (connectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
+ clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+ connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
}
}
availability: AvailabilityType.Operative,
bootStatus: 'Available',
MeterValues: [],
+ transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
+ transactionEndedMeterValuesSetInterval: interval2 as unknown as NodeJS.Timeout,
transactionEventQueue: [],
- transactionMeterValuesSetInterval: interval1 as unknown as NodeJS.Timeout,
+ transactionUpdatedMeterValuesSetInterval: interval1 as unknown as NodeJS.Timeout,
} as unknown as ConnectorStatus)
const station = createMockStationForConfigUtils({ connectors })
assert.strictEqual(result.length, 2)
for (const [, connector] of result) {
- assert.ok(!('transactionMeterValuesSetInterval' in connector))
+ assert.ok(!('transactionEndedMeterValues' in connector))
+ assert.ok(!('transactionEndedMeterValuesSetInterval' in connector))
+ assert.ok(!('transactionUpdatedMeterValuesSetInterval' in connector))
assert.ok(!('transactionEventQueue' in connector))
}
assert.strictEqual(result[0][0], 0)
MeterValues: [],
transactionEventQueue: undefined,
transactionId: 42,
- transactionMeterValuesSetInterval: undefined,
transactionStarted: true,
+ transactionUpdatedMeterValuesSetInterval: undefined,
} as unknown as ConnectorStatus)
const station = createMockStationForConfigUtils({ connectors })
availability: AvailabilityType.Operative,
MeterValues: [],
transactionEventQueue: [],
- transactionMeterValuesSetInterval: undefined,
+ transactionUpdatedMeterValuesSetInterval: undefined,
} as unknown as ConnectorStatus)
const evses = new Map<number, EvseStatus>()
evseConnectors.set(1, {
availability: AvailabilityType.Operative,
MeterValues: [],
+ transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
+ transactionEndedMeterValuesSetInterval: undefined,
transactionEventQueue: [],
- transactionMeterValuesSetInterval: undefined,
+ transactionUpdatedMeterValuesSetInterval: undefined,
} as unknown as ConnectorStatus)
const evses = new Map<number, EvseStatus>()
assert.strictEqual(connectorsStatus.length, 1)
assert.strictEqual(connectorsStatus[0][0], 1)
const connectorStatus = connectorsStatus[0][1]
- assert.ok(!('transactionMeterValuesSetInterval' in connectorStatus))
+ assert.ok(!('transactionEndedMeterValues' in connectorStatus))
+ assert.ok(!('transactionEndedMeterValuesSetInterval' in connectorStatus))
+ assert.ok(!('transactionUpdatedMeterValuesSetInterval' in connectorStatus))
assert.ok(!('transactionEventQueue' in connectorStatus))
})
connectors.set(1, {
availability: AvailabilityType.Operative,
MeterValues: [],
+ transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
+ transactionEndedMeterValuesSetInterval: undefined,
transactionEventQueue: [],
- transactionMeterValuesSetInterval: undefined,
+ transactionUpdatedMeterValuesSetInterval: undefined,
} as unknown as ConnectorStatus)
const station = createMockStationForConfigUtils({ connectors })
assert.strictEqual(result[0].connectorId, 0)
assert.strictEqual(result[1].connectorId, 1)
assert.strictEqual(result[1].connector.availability, AvailabilityType.Operative)
- assert.ok(!('transactionMeterValuesSetInterval' in result[1].connector))
+ assert.ok(!('transactionEndedMeterValues' in result[1].connector))
+ assert.ok(!('transactionEndedMeterValuesSetInterval' in result[1].connector))
+ assert.ok(!('transactionUpdatedMeterValuesSetInterval' in result[1].connector))
assert.ok(!('transactionEventQueue' in result[1].connector))
})
evseConnectors.set(1, {
availability: AvailabilityType.Operative,
MeterValues: [],
+ transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
+ transactionEndedMeterValuesSetInterval: undefined,
transactionEventQueue: [],
- transactionMeterValuesSetInterval: undefined,
+ transactionUpdatedMeterValuesSetInterval: undefined,
} as unknown as ConnectorStatus)
const evses = new Map<number, EvseStatus>()
assert.strictEqual(result[1].evseId, 1)
assert.strictEqual(result[1].connectors.length, 1)
assert.strictEqual(result[1].connectors[0].connectorId, 1)
- assert.ok(!('transactionMeterValuesSetInterval' in result[1].connectors[0].connector))
+ assert.ok(!('transactionEndedMeterValues' in result[1].connectors[0].connector))
+ assert.ok(!('transactionEndedMeterValuesSetInterval' in result[1].connectors[0].connector))
+ assert.ok(!('transactionUpdatedMeterValuesSetInterval' in result[1].connectors[0].connector))
assert.ok(!('transactionEventQueue' in result[1].connectors[0].connector))
})