diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt index 5f72b44b..1f562550 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt @@ -40,7 +40,6 @@ import org.eclipse.kuksa.model.Property import org.eclipse.kuksa.proto.v1.KuksaValV1 import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse import org.eclipse.kuksa.proto.v1.Types -import org.eclipse.kuksa.proto.v1.Types.DataEntry import org.eclipse.kuksa.proto.v1.Types.Datapoint import org.eclipse.kuksa.proto.v1.Types.Field import org.eclipse.kuksa.testapp.databroker.DataBrokerEngine @@ -94,9 +93,15 @@ class KuksaDataBrokerActivity : ComponentActivity() { } private val propertyListener = object : PropertyListener { - override fun onPropertyChanged(vssPath: String, field: Field, updatedValue: DataEntry) { - Log.d(TAG, "onPropertyChanged path: vssPath = $vssPath, field = $field, changedValue = $updatedValue") - outputViewModel.addOutputEntry("Updated value: $updatedValue") + override fun onPropertyChanged(entryUpdates: List) { + Log.d(TAG, "onPropertyChanged() called with: updatedValues = $entryUpdates") + + val entries = mutableListOf().apply { + add("Updated Entries") + addAll(entryUpdates.map { it.entry.toString() }) + } + val outputEntry = OutputEntry(messages = entries) + outputViewModel.addOutputEntry(outputEntry) } override fun onError(throwable: Throwable) { diff --git a/gradle.properties b/gradle.properties index 7572ceb8..2574d3c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,7 @@ android.useAndroidX=true kotlin.code.style=official org.gradle.jvmargs=-Xmx2048m +kotlin.daemon.jvmargs=-Xmx2048m # When using compose + ksp the incremental compiler should be disabled: https://issuetracker.google.com/issues/207185051 ksp.incremental=false diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerTransporter.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerTransporter.kt index c06c43d6..554ca496 100644 --- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerTransporter.kt +++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerTransporter.kt @@ -149,12 +149,8 @@ internal class DataBrokerTransporter( val subscription = Subscription(vssPath, field, cancellableContext) val streamObserver = object : StreamObserver { override fun onNext(value: SubscribeResponse) { - for (entryUpdate in value.updatesList) { - val entry = entryUpdate.entry - - subscription.listeners.forEach { observer -> - observer.onPropertyChanged(vssPath, field, entry) - } + subscription.listeners.forEach { observer -> + observer.onPropertyChanged(value.updatesList) } subscription.lastSubscribeResponse = value diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/PropertyListener.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/PropertyListener.kt index 951ffb4d..50f0af6a 100644 --- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/PropertyListener.kt +++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/PropertyListener.kt @@ -20,18 +20,19 @@ package org.eclipse.kuksa import org.eclipse.kuksa.pattern.listener.Listener -import org.eclipse.kuksa.proto.v1.Types.DataEntry -import org.eclipse.kuksa.proto.v1.Types.Field +import org.eclipse.kuksa.proto.v1.KuksaValV1 import org.eclipse.kuksa.vsscore.model.VssSpecification /** - * The Listener is used to notify about changes to subscribed properties. + * The Listener is used to notify about changes to subscribed properties. When registering the listener to + * Vehicle.ADAS.ABS this listener will also be notified about changes of sub-properties e.g. Vehicle.ADAS.ABS.IsEnabled + * or Vehicle.ADAS.ABS.IsEngaged. */ interface PropertyListener : Listener { /** - * Will be triggered with the [updatedValue] when the underlying [field] of the [vssPath] changed it's value. + * Will be triggered with a list of [entryUpdates] of the corresponding field. */ - fun onPropertyChanged(vssPath: String, field: Field, updatedValue: DataEntry) + fun onPropertyChanged(entryUpdates: List) /** * Will be triggered when an error happens during subscription and forwards the [throwable]. diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriber.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriber.kt index a53b159f..f95678fe 100644 --- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriber.kt +++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriber.kt @@ -25,7 +25,6 @@ import org.eclipse.kuksa.DataBrokerTransporter import org.eclipse.kuksa.PropertyListener import org.eclipse.kuksa.VssSpecificationListener import org.eclipse.kuksa.extension.TAG -import org.eclipse.kuksa.extension.createProperties import org.eclipse.kuksa.proto.v1.Types import org.eclipse.kuksa.proto.v1.Types.Field import org.eclipse.kuksa.vsscore.model.VssProperty @@ -91,13 +90,10 @@ internal class DataBrokerSubscriber(private val dataBrokerTransporter: DataBroke field: Field = Field.FIELD_VALUE, listener: VssSpecificationListener, ) { - val leafProperties = specification.createProperties(field) - val vssPaths = leafProperties.map { it.vssPath } + val vssPath = specification.vssPath - val specificationPropertyListener = SpecificationPropertyListener(specification, vssPaths, listener) - vssPaths.forEach { vssPath -> - subscribe(vssPath, field, specificationPropertyListener) - } + val specificationPropertyListener = SpecificationPropertyListener(specification, listener) + subscribe(vssPath, field, specificationPropertyListener) } /** @@ -111,13 +107,10 @@ internal class DataBrokerSubscriber(private val dataBrokerTransporter: DataBroke field: Field = Field.FIELD_VALUE, listener: VssSpecificationListener, ) { - val leafProperties = specification.createProperties(field) - val vssPaths = leafProperties.map { it.vssPath } + val vssPath = specification.vssPath - val specificationPropertyListener = SpecificationPropertyListener(specification, vssPaths, listener) - vssPaths.forEach { vssPath -> - unsubscribe(vssPath, field, specificationPropertyListener) - } + val specificationPropertyListener = SpecificationPropertyListener(specification, listener) + unsubscribe(vssPath, field, specificationPropertyListener) } private companion object { diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/SpecificationPropertyListener.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/SpecificationPropertyListener.kt index 4de50f5d..b93dfe65 100644 --- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/SpecificationPropertyListener.kt +++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/SpecificationPropertyListener.kt @@ -19,49 +19,27 @@ package org.eclipse.kuksa.subscription -import android.util.Log -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking import org.eclipse.kuksa.PropertyListener import org.eclipse.kuksa.VssSpecificationListener -import org.eclipse.kuksa.extension.TAG import org.eclipse.kuksa.extension.copy -import org.eclipse.kuksa.proto.v1.Types +import org.eclipse.kuksa.proto.v1.KuksaValV1 import org.eclipse.kuksa.vsscore.model.VssSpecification internal class SpecificationPropertyListener( specification: T, - vssPaths: Collection, private val listener: VssSpecificationListener, ) : PropertyListener { - // TODO: Remove as soon as the server supports subscribing to vssPaths which are not VssProperties - // Reduces the load on the observer for big VssSpecifications. We wait for the initial update - // of all VssProperties before notifying the observer about the first batch - private val initialSubscriptionUpdates = vssPaths.associateWith { false }.toMutableMap() - // This is currently needed because we get multiple subscribe responses for every heir. Otherwise we // would override the last heir value with every new response. private var updatedVssSpecification: T = specification - // Multiple onPropertyChanged updates from different threads may be called. The updatedVssSpecification must be - // in sync however. Calling the .copy in a blocking context is necessary for this. - @OptIn(ExperimentalCoroutinesApi::class) - private val specificationUpdateContext = Dispatchers.IO.limitedParallelism(1) - - override fun onPropertyChanged(vssPath: String, field: Types.Field, updatedValue: Types.DataEntry) { - Log.d(TAG, "Update from subscribed property: $vssPath - $field: ${updatedValue.value}") - - runBlocking(specificationUpdateContext) { - updatedVssSpecification = updatedVssSpecification.copy(vssPath, updatedValue.value) + override fun onPropertyChanged(entryUpdates: List) { + entryUpdates.forEach { entryUpdate -> + val dataEntry = entryUpdate.entry + updatedVssSpecification = updatedVssSpecification.copy(dataEntry.path, dataEntry.value) } - initialSubscriptionUpdates[vssPath] = true - val isInitialSubscriptionComplete = initialSubscriptionUpdates.values.all { it } - if (isInitialSubscriptionComplete) { - Log.d(TAG, "Update for subscribed specification complete: ${updatedVssSpecification.vssPath}") - listener.onSpecificationChanged(updatedVssSpecification) - } + listener.onSpecificationChanged(updatedVssSpecification) } override fun onError(throwable: Throwable) { diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/Subscription.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/Subscription.kt index 56419740..6318bfab 100644 --- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/Subscription.kt +++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/subscription/Subscription.kt @@ -48,10 +48,7 @@ internal class Subscription( } else { val lastSubscribeResponse = lastSubscribeResponse ?: return@MultiListener - for (entryUpdate in lastSubscribeResponse.updatesList) { - val entry = entryUpdate.entry - observer.onPropertyChanged(vssPath, field, entry) - } + observer.onPropertyChanged(lastSubscribeResponse.updatesList) } }, ) diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt index 9a936ebd..46f572d3 100644 --- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt @@ -21,6 +21,7 @@ package org.eclipse.kuksa import io.grpc.ConnectivityState import io.grpc.ManagedChannel +import io.kotest.assertions.nondeterministic.eventually import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain @@ -31,7 +32,9 @@ import io.mockk.slot import io.mockk.verify import kotlinx.coroutines.runBlocking import org.eclipse.kuksa.databroker.DataBrokerConnectorProvider +import org.eclipse.kuksa.mocking.FriendlyVssSpecificationListener import org.eclipse.kuksa.model.Property +import org.eclipse.kuksa.proto.v1.KuksaValV1 import org.eclipse.kuksa.proto.v1.Types import org.eclipse.kuksa.proto.v1.Types.Datapoint import org.eclipse.kuksa.test.kotest.Integration @@ -40,6 +43,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import kotlin.random.Random +import kotlin.time.Duration.Companion.seconds class DataBrokerConnectionTest : BehaviorSpec({ tags(Integration) @@ -48,17 +52,23 @@ class DataBrokerConnectionTest : BehaviorSpec({ val dataBrokerConnection = connectToDataBrokerBlocking() and("A Property with a valid VSS Path") { + val vssPath = "Vehicle.Acceleration.Lateral" val fields = listOf(Types.Field.FIELD_VALUE) - val property = Property("Vehicle.Acceleration.Lateral", fields) + val property = Property(vssPath, fields) `when`("Subscribing to the Property") { val propertyListener = mockk(relaxed = true) dataBrokerConnection.subscribe(property, propertyListener) then("The #onPropertyChanged method is triggered") { + val capturingSlot = slot>() verify(timeout = 100L) { - propertyListener.onPropertyChanged(any(), any(), any()) + propertyListener.onPropertyChanged(capture(capturingSlot)) } + + val entryUpdates = capturingSlot.captured + entryUpdates.size shouldBe 1 + entryUpdates[0].entry.path shouldBe vssPath } `when`("The observed Property changes") { @@ -70,14 +80,14 @@ class DataBrokerConnectionTest : BehaviorSpec({ dataBrokerConnection.update(property, datapoint) then("The #onPropertyChanged callback is triggered with the new value") { - val capturingSlot = slot() + val capturingSlot = slot>() verify(timeout = 100) { - propertyListener.onPropertyChanged(any(), any(), capture(capturingSlot)) + propertyListener.onPropertyChanged(capture(capturingSlot)) } - val dataEntry = capturingSlot.captured - val capturedDatapoint = dataEntry.value + val entryUpdates = capturingSlot.captured + val capturedDatapoint = entryUpdates[0].entry.value val float = capturedDatapoint.float assertEquals(newValue, float, 0.0001f) @@ -154,15 +164,13 @@ class DataBrokerConnectionTest : BehaviorSpec({ } `when`("Subscribing to the specification") { - val specificationListener = - mockk>(relaxed = true) + val specificationListener = FriendlyVssSpecificationListener() dataBrokerConnection.subscribe(specification, listener = specificationListener) then("The #onSpecificationChanged method is triggered") { - verify( - timeout = 100L, - exactly = 1, - ) { specificationListener.onSpecificationChanged(any()) } + eventually(1.seconds) { + specificationListener.updatedSpecifications.size shouldBe 1 + } } and("The initial value is different from the default for a child") { @@ -172,13 +180,11 @@ class DataBrokerConnectionTest : BehaviorSpec({ dataBrokerConnection.update(property, datapoint) then("Every child property has been updated with the correct value") { - val capturingList = mutableListOf() - - verify(timeout = 100, exactly = 2) { - specificationListener.onSpecificationChanged(capture(capturingList)) + eventually(1.seconds) { + specificationListener.updatedSpecifications.size shouldBe 2 } - val updatedDriver = capturingList.last() + val updatedDriver = specificationListener.updatedSpecifications.last() val heartRate = updatedDriver.heartRate heartRate.value shouldBe newHeartRateValue @@ -192,13 +198,11 @@ class DataBrokerConnectionTest : BehaviorSpec({ dataBrokerConnection.update(property, datapoint) then("The subscribed Specification should be updated") { - val capturingSlots = mutableListOf() - - verify(timeout = 100, exactly = 3) { - specificationListener.onSpecificationChanged(capture(capturingSlots)) + eventually(1.seconds) { + specificationListener.updatedSpecifications.size shouldBe 3 } - val updatedDriver = capturingSlots.last() + val updatedDriver = specificationListener.updatedSpecifications.last() val heartRate = updatedDriver.heartRate heartRate.value shouldBe newHeartRateValue diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerTransporterTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerTransporterTest.kt index 519b260b..ee2d9191 100644 --- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerTransporterTest.kt +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerTransporterTest.kt @@ -111,7 +111,7 @@ class DataBrokerTransporterTest : BehaviorSpec({ then("The PropertyListener should be notified") { verify { - propertyListener.onPropertyChanged(vssPath, Types.Field.FIELD_VALUE, any()) + propertyListener.onPropertyChanged(any()) } } } diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/extensions/DataBrokerTransporterExtensions.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/extensions/DataBrokerTransporterExtensions.kt index 00678135..8fc91105 100644 --- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/extensions/DataBrokerTransporterExtensions.kt +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/extensions/DataBrokerTransporterExtensions.kt @@ -60,3 +60,22 @@ internal suspend fun DataBrokerTransporter.updateRandomUint32Value( return randomValue } + +internal suspend fun DataBrokerTransporter.toggleBoolean(vssPath: String): Boolean { + val fields = listOf(Types.Field.FIELD_VALUE) + + var newBoolean: Boolean? = null + try { + val response = fetch(vssPath, fields) + val currentBool = response.entriesList[0].value.bool + + newBoolean = !currentBool + val newDatapoint = Types.Datapoint.newBuilder().setBool(newBoolean).build() + + update(vssPath, fields, newDatapoint) + } catch (e: Exception) { + fail("Updating $vssPath to $newBoolean failed: $e") + } + + return newBoolean == true +} diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/mocking/FriendlyPropertyListener.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/mocking/FriendlyPropertyListener.kt new file mode 100644 index 00000000..514f1f5b --- /dev/null +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/mocking/FriendlyPropertyListener.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.eclipse.kuksa.mocking + +import org.eclipse.kuksa.PropertyListener +import org.eclipse.kuksa.proto.v1.KuksaValV1 + +class FriendlyPropertyListener : PropertyListener { + val updates = mutableListOf>() + val errors = mutableListOf() + override fun onPropertyChanged(entryUpdates: List) { + updates.add(entryUpdates) + } + + override fun onError(throwable: Throwable) { + errors.add(throwable) + } + + fun reset() { + updates.clear() + errors.clear() + } +} diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/mocking/FriendlyVssSpecificationListener.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/mocking/FriendlyVssSpecificationListener.kt new file mode 100644 index 00000000..e9a45990 --- /dev/null +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/mocking/FriendlyVssSpecificationListener.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.eclipse.kuksa.mocking + +import org.eclipse.kuksa.VssSpecificationListener +import org.eclipse.kuksa.vsscore.model.VssSpecification + +class FriendlyVssSpecificationListener : VssSpecificationListener { + val updatedSpecifications = mutableListOf() + val errors = mutableListOf() + override fun onSpecificationChanged(vssSpecification: T) { + updatedSpecifications.add(vssSpecification) + } + + override fun onError(throwable: Throwable) { + errors.add(throwable) + } + + fun reset() { + updatedSpecifications.clear() + errors.clear() + } +} diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt index 44c86f03..3e2f976a 100644 --- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt @@ -19,6 +19,8 @@ package org.eclipse.kuksa.subscription +import io.kotest.assertions.nondeterministic.continually +import io.kotest.assertions.nondeterministic.eventually import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.mockk.clearMocks @@ -28,17 +30,20 @@ import io.mockk.verify import kotlinx.coroutines.delay import org.eclipse.kuksa.DataBrokerTransporter import org.eclipse.kuksa.PropertyListener -import org.eclipse.kuksa.VssSpecificationListener import org.eclipse.kuksa.databroker.DataBrokerConnectorProvider +import org.eclipse.kuksa.extensions.toggleBoolean import org.eclipse.kuksa.extensions.updateRandomFloatValue import org.eclipse.kuksa.extensions.updateRandomUint32Value +import org.eclipse.kuksa.mocking.FriendlyPropertyListener +import org.eclipse.kuksa.mocking.FriendlyVssSpecificationListener import org.eclipse.kuksa.pattern.listener.MultiListener import org.eclipse.kuksa.pattern.listener.count import org.eclipse.kuksa.proto.v1.Types -import org.eclipse.kuksa.proto.v1.Types.DataEntry import org.eclipse.kuksa.test.kotest.Insecure import org.eclipse.kuksa.test.kotest.Integration import org.eclipse.kuksa.vssSpecification.VssDriver +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds class DataBrokerSubscriberTest : BehaviorSpec({ tags(Integration, Insecure) @@ -53,46 +58,115 @@ class DataBrokerSubscriberTest : BehaviorSpec({ DataBrokerTransporter(dataBrokerConnectorProvider.managedChannel) val classUnderTest = DataBrokerSubscriber(databrokerTransporter) + `when`("Subscribing to VSS_PATH (Branch) 'Vehicle.ADAS.ABS'") { + val vssPath = "Vehicle.ADAS.ABS" + val fieldValue = Types.Field.FIELD_VALUE + val propertyListener = FriendlyPropertyListener() + classUnderTest.subscribe(vssPath, fieldValue, propertyListener) + + then("The PropertyListener should send out ONE update containing ALL children") { + eventually(1.seconds) { + propertyListener.updates.size shouldBe 1 + } + + val entryUpdates = propertyListener.updates[0] + propertyListener.updates.size shouldBe 1 // ONE update + entryUpdates.size shouldBe 3 // all children: IsEnabled, IsEngaged, IsError + entryUpdates.all { it.entry.path.startsWith(vssPath) } shouldBe true + } + + `when`("Any child changes it's value") { + propertyListener.reset() + + val vssPathIsError = "Vehicle.ADAS.ABS.IsError" + val newValueIsError = databrokerTransporter.toggleBoolean(vssPathIsError) + val vssPathIsEngaged = "Vehicle.ADAS.ABS.IsEngaged" + val newValueIsEngaged = databrokerTransporter.toggleBoolean(vssPathIsEngaged) + + then("The PropertyListener should be notified about it") { + eventually(1.seconds) { + propertyListener.updates.size shouldBe 2 + } + + val entryUpdates = propertyListener.updates.flatten() + entryUpdates.count { + val path = it.entry.path + val entry = it.entry + val value = entry.value + path == vssPathIsError && value.bool == newValueIsError + } shouldBe 1 + entryUpdates.count { + val path = it.entry.path + val entry = it.entry + val value = entry.value + path == vssPathIsEngaged && value.bool == newValueIsEngaged + } shouldBe 1 + } + } + } + `when`("Subscribing using VSS_PATH to Vehicle.Speed with FIELD_VALUE") { val vssPath = "Vehicle.Speed" val fieldValue = Types.Field.FIELD_VALUE - val propertyListener = mockk(relaxed = true) + val propertyListener = FriendlyPropertyListener() classUnderTest.subscribe(vssPath, fieldValue, propertyListener) and("When the FIELD_VALUE of Vehicle.Speed is updated") { - databrokerTransporter.updateRandomFloatValue(vssPath) + val updateRandomFloatValue = databrokerTransporter.updateRandomFloatValue(vssPath) then("The PropertyListener is notified about the change") { - verify(timeout = 100L, exactly = 2) { - propertyListener.onPropertyChanged(vssPath, fieldValue, any()) + eventually(1.seconds) { + propertyListener.updates.size shouldBe 2 } + propertyListener.updates.flatten() + .count { + val dataEntry = it.entry + val datapoint = dataEntry.value + dataEntry.path == vssPath && datapoint.float == updateRandomFloatValue + } shouldBe 1 + + propertyListener.updates.clear() } } `when`("Subscribing the same PropertyListener to a different vssPath") { + val otherVssPath = "Vehicle.ADAS.CruiseControl.SpeedSet" classUnderTest.subscribe(otherVssPath, fieldValue, propertyListener) and("Both values are updated") { - databrokerTransporter.updateRandomFloatValue(vssPath) - databrokerTransporter.updateRandomFloatValue(otherVssPath) + val updatedValueVssPath = databrokerTransporter.updateRandomFloatValue(vssPath) + val updatedValueOtherVssPath = databrokerTransporter.updateRandomFloatValue(otherVssPath) then("The Observer is notified about both changes") { - verify(timeout = 100L, exactly = 3) { - propertyListener.onPropertyChanged(vssPath, fieldValue, any()) - } - verify(timeout = 100L, exactly = 2) { - propertyListener.onPropertyChanged(otherVssPath, fieldValue, any()) + eventually(1.seconds) { + propertyListener.updates.size shouldBe 3 // 1 from subscribe(otherVssPath) + 2 updates } + + val entryUpdates = propertyListener.updates.flatten() + entryUpdates + .count { + val path = it.entry.path + val entry = it.entry + val value = entry.value + path == vssPath && value.float == updatedValueVssPath + } shouldBe 1 + entryUpdates + .count { + val path = it.entry.path + val entry = it.entry + val value = entry.value + path == otherVssPath && value.float == updatedValueOtherVssPath + } shouldBe 1 } } } `when`("Subscribing multiple (different) PropertyListener to $vssPath") { - val propertyListenerMocks = mutableListOf() + val friendlyPropertyListeners = mutableListOf() repeat(10) { - val otherPropertyListenerMock = mockk(relaxed = true) - propertyListenerMocks.add(otherPropertyListenerMock) + val otherPropertyListenerMock = FriendlyPropertyListener() + friendlyPropertyListeners.add(otherPropertyListenerMock) classUnderTest.subscribe(vssPath, fieldValue, otherPropertyListenerMock) } @@ -101,14 +175,13 @@ class DataBrokerSubscriberTest : BehaviorSpec({ val randomFloatValue = databrokerTransporter.updateRandomFloatValue(vssPath) then("Each PropertyListener is only notified once") { - propertyListenerMocks.forEach { propertyListenerMock -> - val dataEntries = mutableListOf() - - verify(timeout = 100L, exactly = 2) { - propertyListenerMock.onPropertyChanged(vssPath, fieldValue, capture(dataEntries)) + friendlyPropertyListeners.forEach { friendlyPropertyListener -> + eventually(1.seconds) { + friendlyPropertyListener.updates.size shouldBe 2 } - val count = dataEntries.count { it.value.float == randomFloatValue } + val count = friendlyPropertyListener.updates + .count { it[0].entry.value.float == randomFloatValue } count shouldBe 1 } } @@ -116,7 +189,7 @@ class DataBrokerSubscriberTest : BehaviorSpec({ } `when`("Unsubscribing the previously registered PropertyListener") { - clearMocks(propertyListener) + propertyListener.reset() classUnderTest.unsubscribe(vssPath, fieldValue, propertyListener) and("When the FIELD_VALUE of Vehicle.Speed is updated") { @@ -124,8 +197,8 @@ class DataBrokerSubscriberTest : BehaviorSpec({ delay(100) then("The PropertyListener is not notified") { - verify(exactly = 0) { - propertyListener.onPropertyChanged(vssPath, fieldValue, any()) + continually(100.milliseconds) { + propertyListener.updates.size shouldBe 0 } } } @@ -135,21 +208,20 @@ class DataBrokerSubscriberTest : BehaviorSpec({ `when`("Subscribing the same PropertyListener twice using VSS_PATH to Vehicle.Speed with FIELD_VALUE") { val vssPath = "Vehicle.Speed" val fieldValue = Types.Field.FIELD_VALUE - val propertyListenerMock = mockk(relaxed = true) - classUnderTest.subscribe(vssPath, fieldValue, propertyListenerMock) - classUnderTest.subscribe(vssPath, fieldValue, propertyListenerMock) + val friendlyPropertyListener = FriendlyPropertyListener() + classUnderTest.subscribe(vssPath, fieldValue, friendlyPropertyListener) + classUnderTest.subscribe(vssPath, fieldValue, friendlyPropertyListener) and("When the FIELD_VALUE of Vehicle.Speed is updated") { val randomFloatValue = databrokerTransporter.updateRandomFloatValue(vssPath) then("The PropertyListener is only notified once") { - val dataEntries = mutableListOf() - - verify(timeout = 100L, exactly = 2) { - propertyListenerMock.onPropertyChanged(vssPath, fieldValue, capture(dataEntries)) + eventually(1.seconds) { + friendlyPropertyListener.updates.size shouldBe 2 } - val count = dataEntries.count { it.value.float == randomFloatValue } + val count = friendlyPropertyListener.updates + .count { it[0].entry.value.float == randomFloatValue } count shouldBe 1 } } @@ -158,12 +230,11 @@ class DataBrokerSubscriberTest : BehaviorSpec({ val specification = VssDriver.VssHeartRate() `when`("Subscribing using VssSpecification to Vehicle.Driver.HeartRate with Field FIELD_VALUE") { - val specificationObserverMock = - mockk>(relaxed = true) + val friendlyVssSpecificationListener = FriendlyVssSpecificationListener() classUnderTest.subscribe( specification, Types.Field.FIELD_VALUE, - specificationObserverMock, + friendlyVssSpecificationListener, ) and("The value of Vehicle.Driver.HeartRate changes") { @@ -171,20 +242,19 @@ class DataBrokerSubscriberTest : BehaviorSpec({ databrokerTransporter.updateRandomUint32Value(specification.vssPath) then("The Observer should be triggered") { - val vssHeartRates = mutableListOf() - verify(timeout = 100, exactly = 2) { - specificationObserverMock.onSpecificationChanged(capture(vssHeartRates)) + eventually(1.seconds) { + friendlyVssSpecificationListener.updatedSpecifications.size shouldBe 2 } - val count = vssHeartRates.count { it.value == randomIntValue } + val count = friendlyVssSpecificationListener.updatedSpecifications + .count { it.value == randomIntValue } count shouldBe 1 } } } `when`("Subscribing the same SpecificationObserver twice to Vehicle.Driver.HeartRate") { - val specificationObserverMock = - mockk>(relaxed = true) + val specificationObserverMock = FriendlyVssSpecificationListener() classUnderTest.subscribe( specification, Types.Field.FIELD_VALUE, @@ -201,13 +271,11 @@ class DataBrokerSubscriberTest : BehaviorSpec({ databrokerTransporter.updateRandomUint32Value(specification.vssPath) then("The Observer is only notified once") { - val heartRates = mutableListOf() - - verify(timeout = 100, exactly = 2) { - specificationObserverMock.onSpecificationChanged(capture(heartRates)) + eventually(1.seconds) { + specificationObserverMock.updatedSpecifications.size shouldBe 2 } - val count = heartRates.count { it.value == randomIntValue } + val count = specificationObserverMock.updatedSpecifications.count { it.value == randomIntValue } count shouldBe 1 } } diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssDriver.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssDriver.kt index 8d87ab8f..6d3f6999 100644 --- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssDriver.kt +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssDriver.kt @@ -23,137 +23,268 @@ import org.eclipse.kuksa.vsscore.model.VssProperty import org.eclipse.kuksa.vsscore.model.VssSpecification import kotlin.reflect.KClass -data class VssVehicle( - val driver: VssDriver = VssDriver(), - val passenger: VssPassenger = VssPassenger(), - val body: VssBody = VssBody(), - override val uuid: String = "Vehicle", - override val vssPath: String = "Vehicle", - override val description: String = "High-level vehicle data.", - override val type: String = "branch", - override val comment: String = "", +data class VssDriver @JvmOverloads constructor( + val attentiveProbability: VssAttentiveProbability = VssAttentiveProbability(), + val distractionLevel: VssDistractionLevel = VssDistractionLevel(), + val fatigueLevel: VssFatigueLevel = VssFatigueLevel(), + val heartRate: VssHeartRate = VssHeartRate(), + val identifier: VssIdentifier = VssIdentifier(), + val isEyesOnRoad: VssIsEyesOnRoad = VssIsEyesOnRoad(), + val isHandsOnWheel: VssIsHandsOnWheel = VssIsHandsOnWheel(), ) : VssSpecification { + override val comment: String + get() = "" + + override val description: String + get() = "Driver data." + + override val type: String + get() = "branch" + + override val uuid: String + get() = "1cac57e7b7e756dc8a154eaacbce6426" + + override val vssPath: String + get() = "Vehicle.Driver" + override val children: Set - get() = setOf(driver, passenger, body) + get() = setOf( + attentiveProbability, + distractionLevel, + fatigueLevel, + heartRate, + identifier, + isEyesOnRoad, + isHandsOnWheel, + ) + + override val parentClass: KClass<*>? + get() = null + + data class VssHeartRate @JvmOverloads constructor( + override val `value`: Int = 0, + ) : VssProperty { + override val comment: String + get() = "" + + override val description: String + get() = "Heart rate of the driver." + + override val type: String + get() = "sensor" + + override val uuid: String + get() = "d71516905f785c4da867a2f86e774d93" + + override val vssPath: String + get() = "Vehicle.Driver.HeartRate" + + override val children: Set + get() = setOf() + + override val parentClass: KClass<*> + get() = VssVehicle::class + } } -data class VssBody( - override val uuid: String = "Body", - override val vssPath: String = "Vehicle.Body", - override val description: String = "All body components.", - override val type: String = "branch", - override val comment: String = "", -) : VssSpecification { +data class VssAttentiveProbability @JvmOverloads constructor( + override val `value`: Float = 0f, +) : VssProperty { + override val comment: String + get() = "" + + override val description: String + get() = "Probability of attentiveness of the driver." + + override val type: String + get() = "sensor" + + override val uuid: String + get() = "fcd202467afb533fbbf9e7da89cc1cee" + + override val vssPath: String + get() = "Vehicle.Driver.AttentiveProbability" + + override val children: Set + get() = setOf() + override val parentClass: KClass<*> get() = VssVehicle::class } -data class VssDriver( - val heartRate: VssHeartRate = VssHeartRate(), - val isEyesOnRoad: VssIsEyesOnRoad = VssIsEyesOnRoad(), - override val uuid: String = "Driver", - override val vssPath: String = "Vehicle.Driver", - override val description: String = "Driver data.", - override val type: String = "branch", - override val comment: String = "", -) : VssSpecification { +data class VssDistractionLevel @JvmOverloads constructor( + override val `value`: Float = 0f, +) : VssProperty { + override val comment: String + get() = "" + + override val description: String + get() = "Distraction level of the driver" + + override val type: String + get() = "sensor" + + override val uuid: String + get() = "cb35ec0b924e58979e1469146d65c3fa" + + override val vssPath: String + get() = "Vehicle.Driver.DistractionLevel" + override val children: Set - get() = setOf(heartRate, isEyesOnRoad) + get() = setOf() + override val parentClass: KClass<*> get() = VssVehicle::class +} - data class VssHeartRate( - override val uuid: String = "Driver HeartRate", - override val vssPath: String = "Vehicle.Driver.HeartRate", - override val description: String = "Heart rate of the driver.", - override val type: String = "sensor", - override val comment: String = "", - override val value: Int = 100, - ) : VssProperty { - override val parentClass: KClass<*> - get() = VssDriver::class - } +data class VssFatigueLevel @JvmOverloads constructor( + override val `value`: Float = 0f, +) : VssProperty { + override val comment: String + get() = "" - data class VssIsEyesOnRoad( - override val uuid: String = "Driver IsEyesOnRoad", - override val vssPath: String = "Vehicle.Driver.IsEyesOnRoad", - override val description: String = "Has driver the eyes on road or not?", - override val type: String = "sensor", - override val comment: String = "", - override val value: Boolean = true, - ) : VssProperty { - override val parentClass: KClass<*> - get() = VssDriver::class - } + override val description: String + get() = "Fatigue level of the driver" + + override val type: String + get() = "sensor" + + override val uuid: String + get() = "49b1626295705a79ae20d8a270c48b6b" + + override val vssPath: String + get() = "Vehicle.Driver.FatigueLevel" + + override val children: Set + get() = setOf() + + override val parentClass: KClass<*> + get() = VssVehicle::class } -data class VssPassenger( - val heartRate: VssHeartRate = VssHeartRate(), - override val uuid: String = "Passenger", - override val vssPath: String = "Vehicle.Passenger", - override val description: String = "Passenger data", - override val type: String = "branch", - override val comment: String = "", +data class VssIdentifier @JvmOverloads constructor( + val issuer: VssIssuer = VssIssuer(), + val subject: VssSubject = VssSubject(), ) : VssSpecification { + override val comment: String + get() = "" + + override val description: String + get() = "Identifier attributes based on OAuth 2.0." + + override val type: String + get() = "branch" + + override val uuid: String + get() = "89705397069c5ec58d607318f2ff0ea8" + + override val vssPath: String + get() = "Vehicle.Driver.Identifier" + override val children: Set - get() = setOf(heartRate) + get() = setOf(issuer, subject) + override val parentClass: KClass<*> get() = VssVehicle::class +} - data class VssHeartRate( - override val uuid: String = "Passenger HeartRate", - override val vssPath: String = "Vehicle.Passenger.HeartRate", - override val description: String = "Heart rate of the Passenger.", - override val type: String = "sensor", - override val comment: String = "", - override val value: Int = 80, - ) : VssProperty { - override val parentClass: KClass<*> - get() = VssPassenger::class - } +data class VssIssuer @JvmOverloads constructor( + override val `value`: String = "", +) : VssProperty { + override val comment: String + get() = "" + + override val description: String + get() = + "Unique Issuer for the authentication of the occupant e.g. https://accounts.funcorp.com." + + override val type: String + get() = "sensor" + + override val uuid: String + get() = "ee7988d26d7156d2a030ecc506ea97e7" + + override val vssPath: String + get() = "Vehicle.Driver.Identifier.Issuer" + + override val children: Set + get() = setOf() + + override val parentClass: KClass<*> + get() = VssIdentifier::class } -data class VssValueInt( - override val uuid: String = "Value", - override val vssPath: String = "Value", - override val description: String = "", - override val type: String = "sensor", - override val comment: String = "", - override val value: Int = 0, -) : VssProperty - -data class VssValueFloat( - override val uuid: String = "Value", - override val vssPath: String = "Value", - override val description: String = "", - override val type: String = "sensor", - override val comment: String = "", - override val value: Float = 0f, -) : VssProperty - -data class VssValueDouble( - override val uuid: String = "Value", - override val vssPath: String = "Value", - override val description: String = "", - override val type: String = "sensor", - override val comment: String = "", - override val value: Double = 0.0, -) : VssProperty - -data class VssValueLong( - override val uuid: String = "Value", - override val vssPath: String = "Value", - override val description: String = "", - override val type: String = "sensor", - override val comment: String = "", - override val value: Long = 0L, -) : VssProperty - -data class VssInvalid( - override val uuid: String = "Invalid", - override val vssPath: String = "Invalid", - override val description: String = "", - override val type: String = "", - override val comment: String = "", - override val value: Exception = Exception(), -) : VssProperty +data class VssSubject @JvmOverloads constructor( + override val `value`: String = "", +) : VssProperty { + override val comment: String + get() = "" + + override val description: String + get() = "Subject for the authentication of the occupant e.g. UserID 7331677." + + override val type: String + get() = "sensor" + + override val uuid: String + get() = "b41ec688af265f10824bc9635989ac55" + + override val vssPath: String + get() = "Vehicle.Driver.Identifier.Subject" + + override val children: Set + get() = setOf() + + override val parentClass: KClass<*> + get() = VssIdentifier::class +} + +data class VssIsEyesOnRoad @JvmOverloads constructor( + override val `value`: Boolean = false, +) : VssProperty { + override val comment: String + get() = "" + + override val description: String + get() = "Has driver the eyes on road or not?" + + override val type: String + get() = "sensor" + + override val uuid: String + get() = "625e5009f1145aa0b797ee6c335ca2fe" + + override val vssPath: String + get() = "Vehicle.Driver.IsEyesOnRoad" + + override val children: Set + get() = setOf() + + override val parentClass: KClass<*> + get() = VssVehicle::class +} + +data class VssIsHandsOnWheel @JvmOverloads constructor( + override val `value`: Boolean = false, +) : VssProperty { + override val comment: String + get() = "" + + override val description: String + get() = "Are the driver's hands on the steering wheel or not?" + + override val type: String + get() = "sensor" + + override val uuid: String + get() = "90d7dc2c408c528d941829ff88075f24" + + override val vssPath: String + get() = "Vehicle.Driver.IsHandsOnWheel" + + override val children: Set + get() = setOf() + + override val parentClass: KClass<*> + get() = VssVehicle::class +} diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssSpecificationCopyTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssSpecificationCopyTest.kt index 7965782f..8499acd2 100644 --- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssSpecificationCopyTest.kt +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssSpecificationCopyTest.kt @@ -58,7 +58,7 @@ class VssSpecificationCopyTest : BehaviorSpec({ val invertedSpecification = !vehicle.driver.isEyesOnRoad then("it should return a copy with the inverted value") { - invertedSpecification.value shouldBe false + invertedSpecification.value shouldBe true } } diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssVehicle.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssVehicle.kt new file mode 100644 index 00000000..6abebf3d --- /dev/null +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/vssSpecification/VssVehicle.kt @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.eclipse.kuksa.vssSpecification + +import org.eclipse.kuksa.vsscore.model.VssProperty +import org.eclipse.kuksa.vsscore.model.VssSpecification +import kotlin.reflect.KClass + +data class VssVehicle( + val driver: VssDriver = VssDriver(), + val passenger: VssPassenger = VssPassenger(), + val body: VssBody = VssBody(), + override val uuid: String = "Vehicle", + override val vssPath: String = "Vehicle", + override val description: String = "High-level vehicle data.", + override val type: String = "branch", + override val comment: String = "", +) : VssSpecification { + override val children: Set + get() = setOf(driver, passenger, body) +} + +data class VssBody( + override val uuid: String = "Body", + override val vssPath: String = "Vehicle.Body", + override val description: String = "All body components.", + override val type: String = "branch", + override val comment: String = "", +) : VssSpecification { + override val parentClass: KClass<*> + get() = VssVehicle::class +} + +data class VssPassenger( + val heartRate: VssHeartRate = VssHeartRate(), + override val uuid: String = "Passenger", + override val vssPath: String = "Vehicle.Passenger", + override val description: String = "Passenger data", + override val type: String = "branch", + override val comment: String = "", +) : VssSpecification { + override val children: Set + get() = setOf(heartRate) + override val parentClass: KClass<*> + get() = VssVehicle::class + + data class VssHeartRate( + override val uuid: String = "Passenger HeartRate", + override val vssPath: String = "Vehicle.Passenger.HeartRate", + override val description: String = "Heart rate of the Passenger.", + override val type: String = "sensor", + override val comment: String = "", + override val value: Int = 80, + ) : VssProperty { + override val parentClass: KClass<*> + get() = VssPassenger::class + } +} + +data class VssValueInt( + override val uuid: String = "Value", + override val vssPath: String = "Value", + override val description: String = "", + override val type: String = "sensor", + override val comment: String = "", + override val value: Int = 0, +) : VssProperty + +data class VssValueFloat( + override val uuid: String = "Value", + override val vssPath: String = "Value", + override val description: String = "", + override val type: String = "sensor", + override val comment: String = "", + override val value: Float = 0f, +) : VssProperty + +data class VssValueDouble( + override val uuid: String = "Value", + override val vssPath: String = "Value", + override val description: String = "", + override val type: String = "sensor", + override val comment: String = "", + override val value: Double = 0.0, +) : VssProperty + +data class VssValueLong( + override val uuid: String = "Value", + override val vssPath: String = "Value", + override val description: String = "", + override val type: String = "sensor", + override val comment: String = "", + override val value: Long = 0L, +) : VssProperty + +data class VssInvalid( + override val uuid: String = "Invalid", + override val vssPath: String = "Invalid", + override val description: String = "", + override val type: String = "", + override val comment: String = "", + override val value: Exception = Exception(), +) : VssProperty diff --git a/samples/src/main/java/com/example/sample/JavaActivity.java b/samples/src/main/java/com/example/sample/JavaActivity.java index 1ac41418..01acaf54 100644 --- a/samples/src/main/java/com/example/sample/JavaActivity.java +++ b/samples/src/main/java/com/example/sample/JavaActivity.java @@ -39,6 +39,7 @@ import java.io.InputStream; import java.util.Collection; import java.util.Collections; +import java.util.List; import javax.annotation.Nullable; @@ -165,11 +166,18 @@ public void subscribeProperty(Property property) { dataBrokerConnection.subscribe(property, new PropertyListener() { @Override - public void onPropertyChanged( - @NonNull String vssPath, - @NonNull Types.Field field, - @NonNull Types.DataEntry updatedValue) { - // handle result + public void onPropertyChanged(@NonNull List entryUpdates) { + for (KuksaValV1.EntryUpdate entryUpdate : entryUpdates) { + Types.DataEntry updatedValue = entryUpdate.getEntry(); + + // handle property change + //noinspection SwitchStatementWithTooFewBranches + switch (updatedValue.getPath()) { + case "VSS.Speed": + float speed = updatedValue.getValue().getFloat(); + + } + } } @Override diff --git a/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt b/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt index 4d4c3061..297e08cf 100644 --- a/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt +++ b/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt @@ -33,6 +33,7 @@ import org.eclipse.kuksa.DisconnectListener import org.eclipse.kuksa.PropertyListener import org.eclipse.kuksa.VssSpecificationListener import org.eclipse.kuksa.model.Property +import org.eclipse.kuksa.proto.v1.KuksaValV1 import org.eclipse.kuksa.proto.v1.Types import org.eclipse.kuksa.proto.v1.Types.Datapoint import org.eclipse.kuksa.vss.VssVehicle @@ -131,11 +132,15 @@ class KotlinActivity : AppCompatActivity() { fun subscribeProperty(property: Property) { val propertyListener = object : PropertyListener { - override fun onPropertyChanged(vssPath: String, field: Types.Field, updatedValue: Types.DataEntry) { - // handle property change - when (vssPath) { - "Vehicle.Speed" -> { - val speed = updatedValue.value.float + override fun onPropertyChanged(entryUpdates: List) { + entryUpdates.forEach { entryUpdate -> + val updatedValue = entryUpdate.entry + + // handle property change + when (updatedValue.path) { + "Vehicle.Speed" -> { + val speed = updatedValue.value.float + } } } }