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..8c28a6bd 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,9 @@ 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") + outputViewModel.addOutputEntry("Updated value: $entryUpdates") } 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 ecea7043..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(entry.path, 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 f2e26c08..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 @@ -29,7 +29,6 @@ import org.eclipse.kuksa.proto.v1.Types import org.eclipse.kuksa.proto.v1.Types.Field import org.eclipse.kuksa.vsscore.model.VssProperty import org.eclipse.kuksa.vsscore.model.VssSpecification -import org.eclipse.kuksa.vsscore.model.vssProperties /** * Creates [Subscription]s to the DataBroker to get notified about changes on the underlying vssPaths and fields. @@ -93,10 +92,7 @@ internal class DataBrokerSubscriber(private val dataBrokerTransporter: DataBroke ) { val vssPath = specification.vssPath - val leafProperties = specification.vssProperties - val childPropertiesPaths = leafProperties.map { it.vssPath } - - val specificationPropertyListener = SpecificationPropertyListener(specification, childPropertiesPaths, listener) + val specificationPropertyListener = SpecificationPropertyListener(specification, listener) subscribe(vssPath, field, specificationPropertyListener) } @@ -113,10 +109,7 @@ internal class DataBrokerSubscriber(private val dataBrokerTransporter: DataBroke ) { val vssPath = specification.vssPath - val leafProperties = specification.vssProperties - val childPropertiesPaths = leafProperties.map { it.vssPath } - - val specificationPropertyListener = SpecificationPropertyListener(specification, childPropertiesPaths, listener) + val specificationPropertyListener = SpecificationPropertyListener(specification, listener) unsubscribe(vssPath, field, specificationPropertyListener) } 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 7c95ec67..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,48 +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 { - // 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 4d4cd6e9..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(entry.path, 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 e00a7dff..e2ae4bd2 100644 --- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt +++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt @@ -32,6 +32,7 @@ import io.mockk.verify import kotlinx.coroutines.runBlocking import org.eclipse.kuksa.databroker.DataBrokerConnectorProvider 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 @@ -48,17 +49,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 +77,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) 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/subscription/DataBrokerSubscriberTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt index 44c86f03..bcc08763 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 @@ -30,12 +30,13 @@ 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.pattern.listener.MultiListener import org.eclipse.kuksa.pattern.listener.count +import org.eclipse.kuksa.proto.v1.KuksaValV1 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 @@ -53,6 +54,55 @@ 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 = mockk(relaxed = true) + classUnderTest.subscribe(vssPath, fieldValue, propertyListener) + + then("The PropertyListener should send out ONE update containing ALL children") { + val entryUpdatesList = mutableListOf>() + verify(timeout = 100, exactly = 1) { + propertyListener.onPropertyChanged(capture(entryUpdatesList)) + } + + val entryUpdates = entryUpdatesList[0] + entryUpdatesList.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") { + clearMocks(propertyListener) + val vssPathIsError = "Vehicle.ADAS.ABS.IsError" + val newValueIsError = databrokerTransporter.toggleBoolean(vssPathIsError) + val vssPathIsEngaged = "Vehicle.ADAS.ABS.IsEngaged" + val newValueIsEngaged = databrokerTransporter.toggleBoolean(vssPathIsEngaged) + delay(50) + + then("The PropertyListener should be notified about it") { + val entryUpdatesList = mutableListOf>() + verify(timeout = 100, exactly = 2) { + propertyListener.onPropertyChanged(capture(entryUpdatesList)) + } + entryUpdatesList.size shouldBe 2 + val entryUpdates = entryUpdatesList.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 @@ -64,26 +114,41 @@ class DataBrokerSubscriberTest : BehaviorSpec({ then("The PropertyListener is notified about the change") { verify(timeout = 100L, exactly = 2) { - propertyListener.onPropertyChanged(vssPath, fieldValue, any()) + propertyListener.onPropertyChanged(any()) } } } `when`("Subscribing the same PropertyListener to a different vssPath") { + clearMocks(propertyListener) 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) + delay(50) then("The Observer is notified about both changes") { + val entryUpdatesList = mutableListOf>() verify(timeout = 100L, exactly = 3) { - propertyListener.onPropertyChanged(vssPath, fieldValue, any()) - } - verify(timeout = 100L, exactly = 2) { - propertyListener.onPropertyChanged(otherVssPath, fieldValue, any()) + propertyListener.onPropertyChanged(capture(entryUpdatesList)) } + val entryUpdates = entryUpdatesList.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 } } } @@ -102,13 +167,13 @@ class DataBrokerSubscriberTest : BehaviorSpec({ then("Each PropertyListener is only notified once") { propertyListenerMocks.forEach { propertyListenerMock -> - val dataEntries = mutableListOf() + val dataEntries = mutableListOf>() verify(timeout = 100L, exactly = 2) { - propertyListenerMock.onPropertyChanged(vssPath, fieldValue, capture(dataEntries)) + propertyListenerMock.onPropertyChanged(capture(dataEntries)) } - val count = dataEntries.count { it.value.float == randomFloatValue } + val count = dataEntries.count { it[0].entry.value.float == randomFloatValue } count shouldBe 1 } } @@ -125,7 +190,7 @@ class DataBrokerSubscriberTest : BehaviorSpec({ then("The PropertyListener is not notified") { verify(exactly = 0) { - propertyListener.onPropertyChanged(vssPath, fieldValue, any()) + propertyListener.onPropertyChanged(any()) } } } @@ -143,13 +208,13 @@ class DataBrokerSubscriberTest : BehaviorSpec({ val randomFloatValue = databrokerTransporter.updateRandomFloatValue(vssPath) then("The PropertyListener is only notified once") { - val dataEntries = mutableListOf() + val dataEntries = mutableListOf>() verify(timeout = 100L, exactly = 2) { - propertyListenerMock.onPropertyChanged(vssPath, fieldValue, capture(dataEntries)) + propertyListenerMock.onPropertyChanged(capture(dataEntries)) } - val count = dataEntries.count { it.value.float == randomFloatValue } + val count = dataEntries.count { it[0].entry.value.float == randomFloatValue } count shouldBe 1 } } 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 + } } } }