Skip to content

Commit cb1e802

Browse files
authored
Merge pull request #91 from IZIVIA/fix/correctly-map-1-6-status-notification
fix: Handle 1.6 statusNotification from updateTransaction
2 parents c05c4e6 + 1001233 commit cb1e802

6 files changed

Lines changed: 240 additions & 41 deletions

File tree

buildSrc/src/main/kotlin/Dependencies.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fun Project.coreProject() {
2828
"implementation"("org.slf4j:slf4j-api:_")
2929

3030
"testImplementation"("org.junit.jupiter:junit-jupiter-api:_")
31+
"testImplementation"("org.junit.jupiter:junit-jupiter-params:_")
3132
"testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:_")
3233

3334
"testImplementation"("io.strikt:strikt-core:_")

generic-api/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
kotlin("jvm")
33
java
44
`maven-publish`
5+
`java-test-fixtures`
56
}
67

78
coreProject()
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package com.izivia.ocpp.api
2+
3+
import com.izivia.ocpp.api.model.common.EVSEType
4+
import com.izivia.ocpp.api.model.common.IdTokenType
5+
import com.izivia.ocpp.api.model.common.MeterValueType
6+
import com.izivia.ocpp.api.model.statusnotification.enumeration.ChargePointErrorCode
7+
import com.izivia.ocpp.api.model.transactionevent.TransactionEventReq
8+
import com.izivia.ocpp.api.model.transactionevent.TransactionType
9+
import com.izivia.ocpp.api.model.transactionevent.enumeration.ChargingStateEnumType
10+
import com.izivia.ocpp.api.model.transactionevent.enumeration.ReasonEnumType
11+
import com.izivia.ocpp.api.model.transactionevent.enumeration.TransactionEventEnumType
12+
import com.izivia.ocpp.api.model.transactionevent.enumeration.TriggerReasonEnumType
13+
import kotlinx.datetime.Clock
14+
import kotlinx.datetime.Instant
15+
16+
@DslMarker
17+
annotation class TransactionDsl
18+
19+
@TransactionDsl
20+
class TransactionEventReqBuilder {
21+
private var eventType: TransactionEventEnumType = TransactionEventEnumType.Started
22+
private var timestamp: Instant = Clock.System.now()
23+
private var triggerReason: TriggerReasonEnumType = TriggerReasonEnumType.Authorized
24+
private var seqNo: Int = 0
25+
private var transactionInfo: TransactionType = TransactionType("0")
26+
private var cableMaxCurrent: Int? = null
27+
private var evse: EVSEType? = null
28+
private var idToken: IdTokenType? = null
29+
private var meterValue: MutableList<MeterValueType>? = null
30+
private var numberOfPhasesUsed: Int? = null
31+
private var offline: Boolean? = null
32+
private var reservationId: Int? = null
33+
34+
fun eventType(value: TransactionEventEnumType) {
35+
eventType = value
36+
}
37+
38+
fun timestamp(value: Instant) {
39+
timestamp = value
40+
}
41+
42+
fun triggerReason(value: TriggerReasonEnumType) {
43+
triggerReason = value
44+
}
45+
46+
fun seqNo(value: Int) {
47+
seqNo = value
48+
}
49+
50+
fun transactionInfo(builder: TransactionTypeBuilder.() -> Unit) {
51+
transactionInfo = TransactionTypeBuilder().apply(builder).build()
52+
}
53+
54+
fun evse(builder: EVSETypeBuilder.() -> Unit) {
55+
evse = EVSETypeBuilder().apply(builder).build()
56+
}
57+
58+
fun cableMaxCurrent(value: Int?) {
59+
cableMaxCurrent = value
60+
}
61+
62+
fun numberOfPhasesUsed(value: Int?) {
63+
numberOfPhasesUsed = value
64+
}
65+
66+
fun offline(value: Boolean?) {
67+
offline = value
68+
}
69+
70+
fun reservationId(value: Int?) {
71+
reservationId = value
72+
}
73+
74+
fun build() = TransactionEventReq(
75+
eventType = eventType,
76+
timestamp = timestamp,
77+
triggerReason = triggerReason,
78+
seqNo = seqNo,
79+
transactionInfo = transactionInfo,
80+
cableMaxCurrent = cableMaxCurrent,
81+
evse = evse,
82+
idToken = idToken,
83+
meterValue = meterValue,
84+
numberOfPhasesUsed = numberOfPhasesUsed,
85+
offline = offline,
86+
reservationId = reservationId
87+
)
88+
}
89+
90+
fun transactionEventReq(builder: TransactionEventReqBuilder.() -> Unit): TransactionEventReq =
91+
TransactionEventReqBuilder().apply(builder).build()
92+
93+
@TransactionDsl
94+
class TransactionTypeBuilder {
95+
private var transactionId: String? = null
96+
private var chargingState: ChargingStateEnumType? = null
97+
private var timeSpentCharging: Int? = null
98+
private var stoppedReason: ReasonEnumType? = null
99+
private var remoteStartId: Int? = null
100+
private var errorCode: ChargePointErrorCode = ChargePointErrorCode.NoError
101+
102+
fun transactionId(value: String) {
103+
transactionId = value
104+
}
105+
106+
fun chargingState(value: ChargingStateEnumType?) {
107+
chargingState = value
108+
}
109+
110+
fun timeSpentCharging(value: Int?) {
111+
timeSpentCharging = value
112+
}
113+
114+
fun stoppedReason(value: ReasonEnumType?) {
115+
stoppedReason = value
116+
}
117+
118+
fun remoteStartId(value: Int?) {
119+
remoteStartId = value
120+
}
121+
122+
fun errorCode(value: ChargePointErrorCode) {
123+
errorCode = value
124+
}
125+
126+
fun build() = TransactionType(
127+
transactionId = requireNotNull(transactionId) { "TransactionType.transactionId is required" },
128+
chargingState = chargingState,
129+
timeSpentCharging = timeSpentCharging,
130+
stoppedReason = stoppedReason,
131+
remoteStartId = remoteStartId,
132+
errorCode = errorCode
133+
)
134+
}
135+
136+
@TransactionDsl
137+
class EVSETypeBuilder {
138+
private var id: Int = 1
139+
private var connectorId: Int? = null
140+
141+
fun id(value: Int) {
142+
id = value
143+
}
144+
145+
fun connectorId(value: Int?) {
146+
connectorId = value
147+
}
148+
149+
fun build() = EVSEType(
150+
id = id,
151+
connectorId = connectorId
152+
)
153+
}

ocpp-1-6-api-adapter/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
implementation("org.mapstruct:mapstruct:_")
1717
kapt("org.mapstruct:mapstruct-processor:_")
1818
runtimeOnly("ch.qos.logback:logback-classic:_")
19+
testImplementation(testFixtures(project(":generic-api")))
1920
}
2021

2122
java {

ocpp-1-6-api-adapter/src/main/kotlin/com/izivia/ocpp/adapter16/mapper/StatusNotificationMapper.kt

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.izivia.ocpp.api.model.statusnotification.enumeration.ConnectorStatusE
44
import com.izivia.ocpp.api.model.transactionevent.TransactionEventReq
55
import com.izivia.ocpp.api.model.transactionevent.TransactionEventResp
66
import com.izivia.ocpp.api.model.transactionevent.enumeration.ChargingStateEnumType
7+
import com.izivia.ocpp.api.model.transactionevent.enumeration.TransactionEventEnumType
78
import com.izivia.ocpp.core16.model.statusnotification.StatusNotificationReq
89
import com.izivia.ocpp.core16.model.statusnotification.StatusNotificationResp
910
import com.izivia.ocpp.core16.model.statusnotification.enumeration.ChargePointErrorCode
@@ -18,28 +19,46 @@ import com.izivia.ocpp.api.model.statusnotification.enumeration.ChargePointError
1819

1920
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = [CommonMapper::class])
2021
abstract class StatusNotificationMapper {
22+
data class ChargingStateWrapper(
23+
val chargingState: ChargingStateEnumType?,
24+
val type: TransactionEventEnumType?
25+
)
26+
27+
fun createChargingStateWrapper(
28+
chargingState: ChargingStateEnumType?,
29+
type: TransactionEventEnumType?
30+
): ChargingStateWrapper = ChargingStateWrapper(chargingState, type)
2131

2232
@Named("convertConnectorStatus")
23-
fun convertConnectorStatus(status : ConnectorStatusEnumType): ChargePointStatus =
24-
when(status){
33+
fun convertConnectorStatus(status: ConnectorStatusEnumType): ChargePointStatus =
34+
when (status) {
2535
ConnectorStatusEnumType.Occupied -> ChargePointStatus.Preparing
2636
else -> ChargePointStatus.valueOf(status.name)
2737
}
2838

2939
@Named("convertErrorCode")
30-
fun convertErrorCode(errorCode : ChargePointErrorCodeGen): ChargePointErrorCode = ChargePointErrorCode.valueOf(errorCode.name)
40+
fun convertErrorCode(errorCode: ChargePointErrorCodeGen): ChargePointErrorCode =
41+
ChargePointErrorCode.valueOf(errorCode.name)
3142

3243
@Named("convertChargingState")
33-
fun convertChargingState(chargingState: ChargingStateEnumType?): ChargePointStatus =
34-
when(chargingState){
35-
ChargingStateEnumType.EVConnected -> ChargePointStatus.Preparing
44+
fun convertChargingState(wrapper: ChargingStateWrapper): ChargePointStatus =
45+
when (wrapper.chargingState) {
46+
ChargingStateEnumType.EVConnected -> when (wrapper.type) {
47+
TransactionEventEnumType.Started -> ChargePointStatus.Preparing
48+
TransactionEventEnumType.Ended -> ChargePointStatus.Finishing
49+
else -> ChargePointStatus.Charging
50+
}
51+
3652
ChargingStateEnumType.Idle -> ChargePointStatus.Available
3753
null -> throw IllegalArgumentException("Argument transactionInfo.chargingState is required in OCPP 1.6 to update a transaction")
38-
else -> ChargePointStatus.valueOf(chargingState.name)
54+
else -> ChargePointStatus.valueOf(wrapper.chargingState.name)
3955
}
4056

4157
@Mapping(target = "connectorId", source = "evse", qualifiedByName = ["convertEVSEType"])
42-
@Mapping(target = "status", source = "transactionInfo.chargingState", qualifiedByName = ["convertChargingState"])
58+
@Mapping(
59+
target = "status",
60+
expression = "java(convertChargingState(createChargingStateWrapper(statusReq.getTransactionInfo().getChargingState(), statusReq.getEventType())))"
61+
)
4362
@Mapping(target = "errorCode", source = "transactionInfo.errorCode", qualifiedByName = ["convertErrorCode"])
4463
abstract fun genToCoreReq(statusReq: TransactionEventReq?): StatusNotificationReq
4564

@@ -50,4 +69,4 @@ abstract class StatusNotificationMapper {
5069
abstract fun genToCoreReq(statusReq: StatusNotificationReqGen?): StatusNotificationReq
5170

5271
abstract fun coreToGenResp(statusResp: StatusNotificationResp): StatusNotificationRespGen
53-
}
72+
}

ocpp-1-6-api-adapter/src/test/kotlin/com/izivia/ocpp/adapter16/test/MapperTest.kt

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,6 @@
11
package com.izivia.ocpp.adapter16.test
22

3-
import com.izivia.ocpp.adapter16.mapper.CancelReservationMapper
4-
import com.izivia.ocpp.adapter16.mapper.ChangeAvailabilityMapper
5-
import com.izivia.ocpp.adapter16.mapper.ChangeConfigurationMapper
6-
import com.izivia.ocpp.adapter16.mapper.ClearCacheMapper
7-
import com.izivia.ocpp.adapter16.mapper.ClearChargingProfileMapper
8-
import com.izivia.ocpp.adapter16.mapper.DataTransferMapper
9-
import com.izivia.ocpp.adapter16.mapper.DiagnosticsStatusNotificationMapper
10-
import com.izivia.ocpp.adapter16.mapper.FirmwareStatusNotificationMapper
11-
import com.izivia.ocpp.adapter16.mapper.GetCompositeScheduleMapper
12-
import com.izivia.ocpp.adapter16.mapper.GetConfigurationMapper
13-
import com.izivia.ocpp.adapter16.mapper.GetDiagnosticsMapper
14-
import com.izivia.ocpp.adapter16.mapper.GetLocalListVersionMapper
15-
import com.izivia.ocpp.adapter16.mapper.RemoteStartTransactionMapper
16-
import com.izivia.ocpp.adapter16.mapper.RemoteStopTransactionMapper
17-
import com.izivia.ocpp.adapter16.mapper.ReserveNowMapper
18-
import com.izivia.ocpp.adapter16.mapper.SendLocalListMapper
19-
import com.izivia.ocpp.adapter16.mapper.SetChargingProfileMapper
20-
import com.izivia.ocpp.adapter16.mapper.TriggerMessageMapper
21-
import com.izivia.ocpp.adapter16.mapper.UnlockConnectorMapper
22-
import com.izivia.ocpp.adapter16.mapper.UpdateFirmwareMapper
3+
import com.izivia.ocpp.adapter16.mapper.*
234
import com.izivia.ocpp.api.model.cancelreservation.CancelReservationResp
245
import com.izivia.ocpp.api.model.cancelreservation.enumeration.CancelReservationStatusEnumType
256
import com.izivia.ocpp.api.model.changeavailability.ChangeAvailabilityResp
@@ -30,18 +11,8 @@ import com.izivia.ocpp.api.model.clearcache.ClearCacheResp
3011
import com.izivia.ocpp.api.model.clearcache.enumeration.ClearCacheStatusEnumType
3112
import com.izivia.ocpp.api.model.clearchargingprofile.ClearChargingProfileResp
3213
import com.izivia.ocpp.api.model.clearchargingprofile.enumeration.ClearChargingProfileStatusEnumType
33-
import com.izivia.ocpp.api.model.common.ChargingSchedulePeriodType
34-
import com.izivia.ocpp.api.model.common.ChargingScheduleType
35-
import com.izivia.ocpp.api.model.common.ComponentType
36-
import com.izivia.ocpp.api.model.common.EVSEType
37-
import com.izivia.ocpp.api.model.common.IdTokenType
38-
import com.izivia.ocpp.api.model.common.StatusInfoType
39-
import com.izivia.ocpp.api.model.common.VariableType
40-
import com.izivia.ocpp.api.model.common.enumeration.ChargingProfilePurposeEnumType
41-
import com.izivia.ocpp.api.model.common.enumeration.ChargingRateUnitEnumType
42-
import com.izivia.ocpp.api.model.common.enumeration.GenericStatusEnumType
43-
import com.izivia.ocpp.api.model.common.enumeration.IdTokenEnumType
44-
import com.izivia.ocpp.api.model.common.enumeration.RequestStartStopStatusEnumType
14+
import com.izivia.ocpp.api.model.common.*
15+
import com.izivia.ocpp.api.model.common.enumeration.*
4516
import com.izivia.ocpp.api.model.datatransfer.DataTransferReq
4617
import com.izivia.ocpp.api.model.datatransfer.enumeration.DataTransferStatusEnumType
4718
import com.izivia.ocpp.api.model.firmwarestatusnotification.FirmwareStatusNotificationReq
@@ -73,12 +44,15 @@ import com.izivia.ocpp.api.model.setchargingprofile.enumeration.ChargingProfileS
7344
import com.izivia.ocpp.api.model.setvariables.SetVariableResultType
7445
import com.izivia.ocpp.api.model.setvariables.SetVariablesResp
7546
import com.izivia.ocpp.api.model.setvariables.enumeration.SetVariableStatusEnumType
47+
import com.izivia.ocpp.api.model.transactionevent.enumeration.ChargingStateEnumType
48+
import com.izivia.ocpp.api.model.transactionevent.enumeration.TransactionEventEnumType
7649
import com.izivia.ocpp.api.model.triggermessage.TriggerMessageResp
7750
import com.izivia.ocpp.api.model.triggermessage.enumeration.MessageTriggerEnumType
7851
import com.izivia.ocpp.api.model.triggermessage.enumeration.TriggerMessageStatusEnumType
7952
import com.izivia.ocpp.api.model.unlockconnector.UnlockConnectorResp
8053
import com.izivia.ocpp.api.model.unlockconnector.enumeration.UnlockStatusEnumType
8154
import com.izivia.ocpp.api.model.updatefirmware.FirmwareType
55+
import com.izivia.ocpp.api.transactionEventReq
8256
import com.izivia.ocpp.core16.model.cancelreservation.CancelReservationReq
8357
import com.izivia.ocpp.core16.model.cancelreservation.enumeration.CancelReservationStatus
8458
import com.izivia.ocpp.core16.model.changeavailability.ChangeAvailabilityReq
@@ -116,6 +90,8 @@ import com.izivia.ocpp.core16.model.sendlocallist.enumeration.UpdateStatus
11690
import com.izivia.ocpp.core16.model.sendlocallist.enumeration.UpdateType
11791
import com.izivia.ocpp.core16.model.setchargingprofile.SetChargingProfileReq
11892
import com.izivia.ocpp.core16.model.setchargingprofile.enumeration.ChargingProfileStatus
93+
import com.izivia.ocpp.core16.model.statusnotification.enumeration.ChargePointErrorCode
94+
import com.izivia.ocpp.core16.model.statusnotification.enumeration.ChargePointStatus
11995
import com.izivia.ocpp.core16.model.triggermessage.TriggerMessageReq
12096
import com.izivia.ocpp.core16.model.triggermessage.enumeration.MessageTrigger
12197
import com.izivia.ocpp.core16.model.triggermessage.enumeration.TriggerMessageStatus
@@ -124,11 +100,17 @@ import com.izivia.ocpp.core16.model.unlockconnector.enumeration.UnlockStatus
124100
import com.izivia.ocpp.core16.model.updatefirmware.UpdateFirmwareReq
125101
import kotlinx.datetime.Instant
126102
import org.junit.jupiter.api.Test
103+
import org.junit.jupiter.api.extension.ExtensionContext
104+
import org.junit.jupiter.params.ParameterizedTest
105+
import org.junit.jupiter.params.provider.Arguments
106+
import org.junit.jupiter.params.provider.ArgumentsProvider
107+
import org.junit.jupiter.params.provider.ArgumentsSource
127108
import org.mapstruct.factory.Mappers
128109
import strikt.api.expectThat
129110
import strikt.api.expectThrows
130111
import strikt.assertions.isA
131112
import strikt.assertions.isEqualTo
113+
import java.util.stream.Stream
132114

133115
class MapperTest {
134116
@Test
@@ -675,4 +657,46 @@ class MapperTest {
675657
.and { get { retries }.isEqualTo(2) }
676658
.and { get { retryInterval }.isEqualTo(3) }
677659
}
660+
661+
@ParameterizedTest
662+
@ArgumentsSource(StatusNotificationReqArgumentsProvider::class)
663+
fun requestStatusNotificationMapper(
664+
eventType: TransactionEventEnumType,
665+
chargingState: ChargingStateEnumType,
666+
expectedStatus: ChargePointStatus
667+
) {
668+
val mapper: StatusNotificationMapper = Mappers.getMapper(StatusNotificationMapper::class.java)
669+
val instant = Instant.parse("2022-02-15T00:00:00.000Z")
670+
val conId = 1
671+
672+
val transactionReq = transactionEventReq {
673+
eventType(eventType)
674+
timestamp(instant)
675+
evse {
676+
connectorId(conId)
677+
}
678+
transactionInfo {
679+
transactionId("0")
680+
chargingState(chargingState)
681+
}
682+
}
683+
684+
val req = mapper.genToCoreReq(transactionReq)
685+
686+
expectThat(req)
687+
.and {
688+
get { status }.isEqualTo(expectedStatus)
689+
get { connectorId }.isEqualTo(conId)
690+
get { errorCode }.isEqualTo(ChargePointErrorCode.NoError)
691+
get { timestamp }.isEqualTo(instant)
692+
}
693+
}
678694
}
695+
696+
class StatusNotificationReqArgumentsProvider : ArgumentsProvider {
697+
override fun provideArguments(context: ExtensionContext): Stream<Arguments> = Stream.of(
698+
Arguments.of(TransactionEventEnumType.Started, ChargingStateEnumType.EVConnected, ChargePointStatus.Preparing),
699+
Arguments.of(TransactionEventEnumType.Ended, ChargingStateEnumType.EVConnected, ChargePointStatus.Finishing)
700+
)
701+
}
702+

0 commit comments

Comments
 (0)