From 852034b3edce4fbb87cb0069914dd2d36df3f95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mark=20Hu=CC=88sers?= Date: Tue, 21 Nov 2023 16:10:07 +0100 Subject: [PATCH] chore: Add more java sample code --- README.md | 40 ++-- docs/QUICKSTART.md | 223 +++++++++++++++--- samples/build.gradle.kts | 4 +- .../java/com/example/sample/JavaActivity.java | 112 ++++++++- .../com/example/sample/KotlinActivity.kt | 16 +- 5 files changed, 324 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 6184f715..b79ac9bf 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This is an Android SDK for the [KUKSA Vehicle Abstraction Layer](https://github. ## Overview -The Kuksa Android SDK allows you to interact with [VSS data](https://github.com/COVESA/vehicle_signal_specification) +The Kuksa Android SDK allows you to interact with [VSS data](https://covesa.github.io/vehicle_signal_specification/) from the [KUKSA Databroker](https://github.com/eclipse/kuksa.val/tree/master/kuksa_databroker) within an Android App. The main functionality consists of fetching, updating and subscribing to VSS data. @@ -16,18 +16,20 @@ within an Android App. The main functionality consists of fetching, updating and *build.gradle* ``` -implementation(org.eclipse.kuksa:kuksa-sdk:) +implementation("org.eclipse.kuksa:kuksa-sdk:") ``` The latest release version can be seen [here](https://github.com/eclipse-kuksa/kuksa-android-sdk/releases). +Snapshot builds are also available (but of course less stable): [Package view](https://github.com/eclipse-kuksa/kuksa-android-sdk/packages/1986280/versions) + See the [Quickstart guide](https://github.com/eclipse-kuksa/kuksa-android-sdk/tree/main/docs/QUICKSTART.md) for additional integration options. ### GitHub packages -The Kuksa SDK is currently uploaded to GitHub packages where an authentication is needed to download the dependency. -It is recommended to not check these information into your version control. +The Kuksa SDK is currently uploaded to [GitHub packages](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#authenticating-to-github-packages) +where an authentication is needed to download the dependency. ``` maven { @@ -41,7 +43,10 @@ maven { ## Usage -``` +> [!NOTE] +> The following snippet expects an unsecure setup of the Databroker. Please see the [QUICKSTART](https://github.com/eclipse-kuksa/kuksa-android-sdk/blob/main/docs/QUICKSTART.md) guide. + +```kotlin private var dataBrokerConnection: DataBrokerConnection? = null fun connectInsecure(host: String, port: Int) { @@ -51,21 +56,21 @@ fun connectInsecure(host: String, port: Int) { .build() val connector = DataBrokerConnector(managedChannel) - dataBrokerConnection = connector.connect() - // Connection to the DataBroker successfully established - } catch (e: DataBrokerException) { - // Connection to the DataBroker failed - } + dataBrokerConnection = connector.connect() + // Connection to the DataBroker successfully established + } catch (e: DataBrokerException) { + // Connection to the DataBroker failed } } ``` -``` +```kotlin fun fetch() { lifecycleScope.launch { - val property = Property("Vehicle.Speed") + val property = Property("Vehicle.Speed", listOf(Field.FIELD_VALUE)) val response = dataBrokerConnection?.fetch(property) ?: return@launch - val value = response.firstValue + val entry = entriesList.first() // Don't forget to handle empty responses + val value = entry.value val speed = value.float } } @@ -78,14 +83,15 @@ further insight into the Kuksa SDK API. You can also checkout the [sample](https ## Requirements - A working setup requires at least a running Kuksa [Databroker](https://github.com/eclipse/kuksa.val/tree/master/kuksa_databroker) -- Optional: The [Kuksa Databroker CLI](https://github.com/eclipse/kuksa.val/tree/master/kuksa_databroker) can be used to manually feed data and test your app. -- Optional: The [Kuksa (Mock)service](https://github.com/eclipse/kuksa.val.services/tree/main/mock_service) can be used to simulate a "real" environment. +- Optional: The [Kuksa Databroker CLI](https://github.com/eclipse/kuksa.val/tree/master/kuksa_databroker) can be used to manually feed data and test your app. + See [this chapter](https://github.com/eclipse/kuksa.val/tree/master/kuksa_databroker#reading-and-writing-vss-data-using-the-cli) on how to read and write data via the CLI. +- Optional: The [(Vehicle) Mock Service](https://github.com/eclipse/kuksa.val.services/tree/main/mock_service) can be used to simulate a "real" environment. ## Contribution -Please feel free create [GitHub issues](https://github.com/eclipse-kuksa/kuksa-android-sdk/issues) and contribute +Please feel free to create [GitHub issues](https://github.com/eclipse-kuksa/kuksa-android-sdk/issues) and contribute [(Guidelines)](https://github.com/eclipse-kuksa/kuksa-android-sdk/blob/main/docs/CONTRIBUTING.md). ## License -The Kuksa Android SDK is provided under the terms of the [Apache Software License 2.0](https://github.com/eclipse/kuksa.val/blob/master/LICENSE). +The Kuksa Android SDK is provided under the terms of the [Apache Software License 2.0](https://github.com/eclipse-kuksa/kuksa-android-sdk/blob/main/LICENSE). diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 000d27aa..6c99a8d1 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,18 +1,22 @@ ## Introduction +Get instantly bootstrapped into the world of the Kuksa SDK with the following code snippets! + ## Integration *build.gradle* ``` -implementation(org.eclipse.kuksa:kuksa-sdk:) +implementation("org.eclipse.kuksa:kuksa-sdk:") ``` ## Connecting to the DataBroker -You can use the following snippet for a simple (unsecure) connection to the DataBroker. See the samples package for -a detailed implementation or how to connect in a secure way with a certificate. +You can use the following snippet for a simple (unsecure) connection to the DataBroker. This highly depends on your +setup so see the [samples package](https://github.com/eclipse-kuksa/kuksa-android-sdk/blob/main/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt) +for a detailed implementation or how to connect in a secure way with a certificate. -``` +*Kotlin* +```kotlin private var dataBrokerConnection: DataBrokerConnection? = null fun connectInsecure(host: String, port: Int) { @@ -31,67 +35,150 @@ fun connectInsecure(host: String, port: Int) { } } ``` +*Java* +```java +void connectInsecure(String host, int port) { + ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(host, port) + .usePlaintext() + .build(); + + DataBrokerConnector connector = new DataBrokerConnector(managedChannel); + connector.connect(new CoroutineCallback() { + @Override + public void onSuccess(DataBrokerConnection result) { + dataBrokerConnection = result; + } + + @Override + public void onError(@NonNull Throwable error) { + // Connection to the DataBroker failed + } + }); +} +``` ## Interacting with the DataBroker -``` +*Kotlin* +```kotlin fun fetch() { lifecycleScope.launch { - val property = Property("Vehicle.Speed") + val property = Property("Vehicle.Speed", listOf(Field.FIELD_VALUE)) val response = dataBrokerConnection?.fetch(property) ?: return@launch - val value = response.firstValue + val entry = response.entriesList.first() // Don't forget to handle empty responses + val value = entry.value val speed = value.float } } -fun update(property: Property, datapoint: Datapoint) { +fun update() { lifecycleScope.launch { - val property = Property("Vehicle.Speed") - val datapoint = Datapoint.newBuilder().float = 50f + val property = Property("Vehicle.Speed", listOf(Field.FIELD_VALUE)) + val datapoint = Datapoint.newBuilder().setFloat(100f).build() dataBrokerConnection?.update(property, datapoint) } } - fun subscribe(property: Property) { - val propertyListener = object : PropertyListener { - override fun onPropertyChanged(vssPath: String, field: Types.Field, updatedValue: Types.DataEntry) { - when (vssPath) { - "Vehicle.Speed" -> { - val speed = updatedValue.value.float - } +fun subscribe() { + val property = Property("Vehicle.Speed", listOf(Field.FIELD_VALUE)) + val propertyListener = object : PropertyListener { + override fun onPropertyChanged(vssPath: String, field: Types.Field, updatedValue: Types.DataEntry) { + when (vssPath) { + "Vehicle.Speed" -> { + val speed = updatedValue.value.float } } } - - dataBrokerConnection?.subscribe(property, propertyListener) } + + dataBrokerConnection?.subscribe(property, propertyListener) +} +``` +*Java* +```java +void fetch() { + Property property = new Property("Vehicle.Speed", Collections.singleton(Types.Field.FIELD_VALUE)); + dataBrokerConnection.fetch(property, new CoroutineCallback() { + @Override + public void onSuccess(GetResponse result) { + result.entriesList.first() // Don't forget to handle empty responses + Types.DataEntry dataEntry = result.getEntriesList().get(0); + Datapoint datapoint = dataEntry.getValue(); + float speed = datapoint.getFloat(); + } + }); +} + +void update() { + Property property = new Property("Vehicle.Speed", Collections.singleton(Types.Field.FIELD_VALUE)); + Datapoint datapoint = Datapoint.newBuilder().setFloat(100f).build(); + dataBrokerConnection.update(property, datapoint, new CoroutineCallback() { + @Override + public void onSuccess(KuksaValV1.SetResponse result) { + // handle result + } + }); +} + +void subscribe() { + Property property = new Property("Vehicle.Speed", Collections.singleton(Types.Field.FIELD_VALUE)); + dataBrokerConnection.subscribe(property, new PropertyListener() { + @Override + public void onPropertyChanged( + @NonNull String vssPath, + @NonNull Types.Field field, + @NonNull Types.DataEntry updatedValue) { + + switch (vssPath) { + case "Vehicle.Speed": + float speed = updatedValue.getValue().getFloat(); + } + } + + @Override + public void onError(@NonNull Throwable throwable) { + // handle error + } + }); +} ``` ## Specifications Symbol Processing The generic nature of using the `Property` API will result into an information loss of the type which can be seen in -the `subscribe` example. It requires an additional check of the `vssPath` when receiving an updated value to correctly -cast it back. This is feasible for simple use cases but can get tedious when working with a lot of vehicle signals. +the `subscribe` example. You may choose to reuse the same listener for multiple properties. Then it requires an additional check +of the `vssPath` after receiving an updated value to correctly cast it back. This is feasible for simple use cases but can get tedious when working with a lot of vehicle signals. -For a more convenient usage you can opt in to auto generate Kotlin models of the same specification the DataBroker uses. +For a more convenient usage you can opt in to auto generate Kotlin models via [Symbol Processing](https://kotlinlang.org/docs/ksp-quickstart.html) +of the same specification the DataBroker uses. For starters you can retrieve an extensive default specification from the +release page of the [COVESA Vehicle Signal Specification GitHub repository](https://github.com/COVESA/vehicle_signal_specification/releases). *build.gradle* ``` -ksp(org.eclipse.kuksa:vss-processor:) +ksp("org.eclipse.kuksa:vss-processor:") ``` -Use the new `VssDefinition` annotation and provide the path to the specification file (Inside the assets folder). -This will generate a complete tree of Kotlin models which can be used in combination with the SDK API. This way you can -work safe types which are also returned again when interacting with the Kuksa SDK. There is also a whole set of -convenience operators and methods to work with the tree data. See the `VssSpecification` class documentation for this. +Use the new [`VssDefinition`](https://github.com/eclipse-kuksa/kuksa-android-sdk/blob/main/vss-core/src/main/java/org/eclipse/kuksa/vsscore/annotation/VssDefinition.kt) annotation and provide the path to the specification file (Inside the assets folder). +Doing so will generate a complete tree of Kotlin models which can be used in combination with the SDK API. This way you can +work with safe types and the SDK takes care of all the model parsing for you. There is also a whole set of +convenience operators and extension methods to work with to manipulate the tree data. See the `VssSpecification` class documentation for this. -``` +*Kotlin* +```kotlin @VssDefinition("vss_rel_4.0.yaml") -class MainActivity +class KotlinActivity +``` +*Java* +```java +@VssDefinition(vssDefinitionPath = "vss_rel_4.0.yaml") +public class JavaActivity ``` +> [!IMPORTANT] +> Keep in mind to always synchronize the specification file between the client and the DataBroker. + *Example .yaml specification file* -``` +```yaml Vehicle.Speed: datatype: float description: Vehicle speed. @@ -101,8 +188,8 @@ Vehicle.Speed: ``` *Example model* -``` -public data class VssSpeed @JvmOverloads constructor( +```kotlin +data class VssSpeed @JvmOverloads constructor( override val `value`: Float = 0f, ) : VssProperty { override val comment: String @@ -125,12 +212,13 @@ public data class VssSpeed @JvmOverloads constructor( override val parentClass: KClass<*>? get() = VssVehicle::class - } } ``` -*Specification API* -``` +### Specification API + +*Kotlin* +```kotlin fun fetch() { lifecycleScope.launch { val vssSpeed = VssVehicle.VssSpeed() @@ -142,7 +230,7 @@ fun fetch() { fun update() { lifecycleScope.launch { val vssSpeed = VssVehicle.VssSpeed(value = 100f) - val updatedSpeed = dataBrokerConnection?.update(vssSpeed) + dataBrokerConnection?.update(vssSpeed) } } @@ -152,6 +240,69 @@ fun subscribe() { override fun onSpecificationChanged(vssSpecification: VssVehicle.VssSpeed) { val speed = vssSpeed.value } + + override fun onError(throwable: Throwable) { + // handle error + } }) } ``` +*Java* +```java +void fetch() { + VssVehicle.VssSpeed vssSpeed = new VssVehicle.VssSpeed(); + dataBrokerConnection.fetch( + vssSpeed, + Collections.singleton(Types.Field.FIELD_VALUE), + new CoroutineCallback() { + @Override + public void onSuccess(@Nullable VssVehicle.VssSpeed result) { + Float speed = result.getValue(); + } + + @Override + public void onError(@NonNull Throwable error) { + // handle error + } + } + ); +} + +void update() { + VssVehicle.VssSpeed vssSpeed = new VssVehicle.VssSpeed(100f); + dataBrokerConnection.update( + vssSpeed, + Collections.singleton(Types.Field.FIELD_VALUE), + new CoroutineCallback>() { + @Override + public void onSuccess(@Nullable Collection result) { + // handle result + } + + @Override + public void onError(@NonNull Throwable error) { + // handle error + } + } + ); +} + +void subscribe() { + VssVehicle.VssSpeed vssSpeed = new VssVehicle.VssSpeed(); + dataBrokerConnection.subscribe( + vssSpeed, + Collections.singleton(Types.Field.FIELD_VALUE), + new VssSpecificationListener() { + @Override + public void onSpecificationChanged(@NonNull VssVehicle.VssSpeed vssSpecification) { + Float speed = vssSpecification.getValue(); + } + + @Override + public void onError(@NonNull Throwable throwable) { + // handle error + } + } + ); +} +``` diff --git a/samples/build.gradle.kts b/samples/build.gradle.kts index 1dfd6936..84abfc8c 100644 --- a/samples/build.gradle.kts +++ b/samples/build.gradle.kts @@ -36,8 +36,8 @@ android { } dependencies { - implementation(project(":kuksa-sdk")) - ksp(project(":vss-processor")) + implementation(project(":kuksa-sdk")) // org.eclipse.kuksa.kuksa-sdk: + ksp(project(":vss-processor")) // org.eclipse.kuksa.vss-processor: // app dependencies implementation(libs.androidx.appcompat) diff --git a/samples/src/main/java/com/example/sample/JavaActivity.java b/samples/src/main/java/com/example/sample/JavaActivity.java index 8e2754d3..1ac41418 100644 --- a/samples/src/main/java/com/example/sample/JavaActivity.java +++ b/samples/src/main/java/com/example/sample/JavaActivity.java @@ -26,13 +26,19 @@ import org.eclipse.kuksa.DataBrokerConnection; import org.eclipse.kuksa.DataBrokerConnector; 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.KuksaValV1.GetResponse; +import org.eclipse.kuksa.proto.v1.Types; import org.eclipse.kuksa.proto.v1.Types.Datapoint; +import org.eclipse.kuksa.vss.VssVehicle; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; import javax.annotation.Nullable; @@ -42,7 +48,10 @@ import io.grpc.ManagedChannelBuilder; import io.grpc.TlsChannelCredentials; -/** @noinspection unused*/ +/** + * @noinspection unused + */ +//@VssDefinition(vssDefinitionPath = "vss_rel_4.0.yaml") // Commented out to prevent conflicts with the Kotlin activity public class JavaActivity extends AppCompatActivity { private final DisconnectListener disconnectListener = () -> { @@ -111,10 +120,16 @@ public void onError(@NonNull Throwable error) { }); } + public void disconnect() { + if (dataBrokerConnection == null) return; + + dataBrokerConnection.getDisconnectListeners().unregister(disconnectListener); + dataBrokerConnection.disconnect(); + dataBrokerConnection = null; + } + public void fetchProperty(Property property) { - if (dataBrokerConnection == null) { - return; - } + if (dataBrokerConnection == null) return; dataBrokerConnection.fetch(property, new CoroutineCallback() { @Override @@ -130,9 +145,7 @@ public void onError(@NonNull Throwable throwable) { } public void updateProperty(Property property, Datapoint datapoint) { - if (dataBrokerConnection == null) { - return; - } + if (dataBrokerConnection == null) return; dataBrokerConnection.update(property, datapoint, new CoroutineCallback() { @Override @@ -145,14 +158,91 @@ public void onError(@NonNull Throwable error) { // handle error } }); + } + + public void subscribeProperty(Property property) { + if (dataBrokerConnection == null) return; + + dataBrokerConnection.subscribe(property, new PropertyListener() { + @Override + public void onPropertyChanged( + @NonNull String vssPath, + @NonNull Types.Field field, + @NonNull Types.DataEntry updatedValue) { + // handle result + } + + @Override + public void onError(@NonNull Throwable throwable) { + // handle error + } + }); + } + + // region: Specifications + public void fetchSpecification() { + if (dataBrokerConnection == null) return; + + VssVehicle.VssSpeed vssSpeed = new VssVehicle.VssSpeed(); + dataBrokerConnection.fetch( + vssSpeed, + Collections.singleton(Types.Field.FIELD_VALUE), + new CoroutineCallback() { + @Override + public void onSuccess(@Nullable VssVehicle.VssSpeed result) { + if (result == null) return; + + Float speed = result.getValue(); + } + + @Override + public void onError(@NonNull Throwable error) { + // handle error + } + } + ); + } + public void updateSpecification() { + if (dataBrokerConnection == null) return; + + VssVehicle.VssSpeed vssSpeed = new VssVehicle.VssSpeed(100f); + dataBrokerConnection.update( + vssSpeed, + Collections.singleton(Types.Field.FIELD_VALUE), + new CoroutineCallback>() { + @Override + public void onSuccess(@Nullable Collection result) { + // handle result + } + + @Override + public void onError(@NonNull Throwable error) { + // handle error + } + } + ); } - public void disconnect() { + public void subscribeSpecification() { if (dataBrokerConnection == null) return; - dataBrokerConnection.getDisconnectListeners().unregister(disconnectListener); - dataBrokerConnection.disconnect(); - dataBrokerConnection = null; + VssVehicle.VssSpeed vssSpeed = new VssVehicle.VssSpeed(); + dataBrokerConnection.subscribe( + vssSpeed, + Collections.singleton(Types.Field.FIELD_VALUE), + new VssSpecificationListener() { + @Override + public void onSpecificationChanged(@NonNull VssVehicle.VssSpeed vssSpecification) { + Float speed = vssSpecification.getValue(); + } + + @Override + public void onError(@NonNull Throwable throwable) { + // handle error + } + } + ); } + // endregion } diff --git a/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt b/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt index 55319ee9..4d4c3061 100644 --- a/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt +++ b/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt @@ -58,6 +58,7 @@ class KotlinActivity : AppCompatActivity() { val connector = DataBrokerConnector(managedChannel) try { dataBrokerConnection = connector.connect() + dataBrokerConnection?.disconnectListeners?.register(disconnectListener) // Connection to DataBroker successfully established } catch (e: DataBrokerException) { // Connection to DataBroker failed @@ -112,7 +113,7 @@ class KotlinActivity : AppCompatActivity() { val response = dataBrokerConnection?.fetch(property) ?: return@launch // handle response } catch (e: DataBrokerException) { - // handle errors + // handle error } } } @@ -123,7 +124,7 @@ class KotlinActivity : AppCompatActivity() { val response = dataBrokerConnection?.update(property, datapoint) ?: return@launch // handle response } catch (e: DataBrokerException) { - // handle errors + // handle error } } } @@ -152,10 +153,10 @@ class KotlinActivity : AppCompatActivity() { lifecycleScope.launch { try { val vssSpeed = VssVehicle.VssSpeed() - val updatedSpeed = dataBrokerConnection?.fetch(vssSpeed) + val updatedSpeed = dataBrokerConnection?.fetch(vssSpeed, listOf(Types.Field.FIELD_VALUE)) val speed = updatedSpeed?.value } catch (e: DataBrokerException) { - // handle errors + // handle error } } } @@ -163,7 +164,7 @@ class KotlinActivity : AppCompatActivity() { fun updateSpecification() { lifecycleScope.launch { val vssSpeed = VssVehicle.VssSpeed(value = 100f) - val updatedSpeed = dataBrokerConnection?.update(vssSpeed) + dataBrokerConnection?.update(vssSpeed, listOf(Types.Field.FIELD_VALUE)) } } @@ -171,10 +172,15 @@ class KotlinActivity : AppCompatActivity() { val vssSpeed = VssVehicle.VssSpeed(value = 100f) dataBrokerConnection?.subscribe( vssSpeed, + listOf(Types.Field.FIELD_VALUE), listener = object : VssSpecificationListener { override fun onSpecificationChanged(vssSpecification: VssVehicle.VssSpeed) { val speed = vssSpeed.value } + + override fun onError(throwable: Throwable) { + // handle error + } }, ) }