Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add update API for generated specifications #27

Merged
merged 19 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import org.eclipse.kuksa.vsscore.annotation.VssDefinition
import org.eclipse.kuksa.vsscore.model.VssSpecification

@VssDefinition("vss_rel_4.0.yaml")
@Suppress("complexity:TooManyFunctions")
class KuksaDataBrokerActivity : ComponentActivity() {
private lateinit var connectionInfoRepository: ConnectionInfoRepository

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo
import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel
import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel.*
import org.eclipse.kuksa.testapp.extension.compose.Headline
import org.eclipse.kuksa.testapp.extension.compose.rememberCountdown
import org.eclipse.kuksa.testapp.extension.compose.RememberCountdown
import org.eclipse.kuksa.testapp.extension.fetchFileName
import org.eclipse.kuksa.testapp.preferences.ConnectionInfoRepository

Expand Down Expand Up @@ -216,7 +216,7 @@ fun DataBrokerConnection(viewModel: ConnectionViewModel) {
onClick = { },
modifier = Modifier.requiredWidth(MinimumButtonWidth),
) {
val timeout by rememberCountdown(initialMillis = viewModel.connectionTimeoutMillis)
val timeout by RememberCountdown(initialMillis = viewModel.connectionTimeoutMillis)

@Suppress("MagicNumber") // To seconds
val timeoutSeconds = timeout / 1000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive

@Composable
fun rememberCountdown(
fun RememberCountdown(
initialMillis: Long,
step: Long = 1000,
): MutableState<Long> {
Expand Down
7 changes: 4 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
android.defaults.buildfeatures.buildconfig=true
android.enableJetifier=true
android.nonFinalResIds=false
android.nonTransitiveRClass=false

android.nonTransitiveRClass=true
android.useAndroidX=true

kotlin.code.style=official

org.gradle.jvmargs=-Xmx2048m

# When using compose + ksp the incremental compiler should be disabled: https://issuetracker.google.com/issues/207185051
Expand Down
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[versions]
activityKtx = "1.7.2"
activityKtx = "1.8.0"
androidGradlePlugin = "8.1.2" # Check with detekt table first: https://detekt.dev/docs/introduction/compatibility/
datastore = "1.0.0"
constraintlayoutCompose = "1.0.1"
datastorePreferences = "1.0.0"
kotlin = "1.9.0"
kotlinpoet = "1.14.2"
kotlinxSerializationJson = "1.6.0"
runtimeLivedata = "1.5.2"
runtimeLivedata = "1.5.3"
symbolProcessingApi = "1.9.10-1.0.13"
tomcatAnnotations = "6.0.53"
detekt = "1.23.1"
Expand All @@ -21,7 +21,7 @@ mockk = "1.13.7"
androidxLifecycle = "2.6.2"
kotlinxCoroutines = "1.7.3"
kotlinCompilerExtension = "1.5.1"
composeBom = "2023.09.02"
composeBom = "2023.10.00"
jvmTarget = "17"

[libraries]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.kuksa.extension.TAG
import org.eclipse.kuksa.extension.copy
import org.eclipse.kuksa.extension.datapoint
import org.eclipse.kuksa.model.Property
import org.eclipse.kuksa.pattern.listener.MultiListener
import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse
Expand All @@ -37,6 +38,7 @@ import org.eclipse.kuksa.subscription.DataBrokerSubscriber
import org.eclipse.kuksa.vsscore.model.VssProperty
import org.eclipse.kuksa.vsscore.model.VssSpecification
import org.eclipse.kuksa.vsscore.model.heritage
import org.eclipse.kuksa.vsscore.model.vssProperties

/**
* The DataBrokerConnection holds an active connection to the DataBroker. The Connection can be use to interact with the
Expand Down Expand Up @@ -194,10 +196,31 @@ class DataBrokerConnection internal constructor(
*/
suspend fun update(
property: Property,
updatedDatapoint: Datapoint,
datapoint: Datapoint,
): SetResponse {
Log.d(TAG, "updateProperty() called with: updatedProperty = $property")
return dataBrokerTransporter.update(property.vssPath, property.fields, updatedDatapoint)
return dataBrokerTransporter.update(property.vssPath, property.fields, datapoint)
}

/**
* Only a [VssProperty] can be updated because they have an actual value. When provided with any parent
* [VssSpecification] then this [update] method will find all [VssProperty] children and updates them instead.
* Compared to [update] with only one [Property] and [Datapoint], here multiple [SetResponse] will be returned
* because a [VssSpecification] may consists of multiple values which may need to be updated.
*
* @throws DataBrokerException in case the connection to the DataBroker is no longer active
* @throws IllegalArgumentException if the [VssProperty] could not be converted to a [Datapoint].
*/
suspend fun update(vssSpecification: VssSpecification): List<SetResponse> {
val responses = mutableListOf<SetResponse>()

vssSpecification.vssProperties.forEach { vssProperty ->
val property = Property(vssProperty.vssPath)
val response = update(property, vssProperty.datapoint)
responses.add(response)
}

return responses
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ internal class DataBrokerTransporter(
*/
suspend fun fetch(
vssPath: String,
fields: List<Field>,
fields: Collection<Field>,
): KuksaValV1.GetResponse {
return withContext(defaultDispatcher) {
val blockingStub = VALGrpc.newBlockingStub(managedChannel)
Expand Down Expand Up @@ -89,7 +89,7 @@ internal class DataBrokerTransporter(
*/
suspend fun update(
vssPath: String,
fields: List<Field>,
fields: Collection<Field>,
updatedDatapoint: Types.Datapoint,
): KuksaValV1.SetResponse {
return withContext(defaultDispatcher) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

package org.eclipse.kuksa.extension

import android.util.Log
import kotlin.reflect.KParameter
import kotlin.reflect.full.instanceParameter
import kotlin.reflect.full.memberFunctions
Expand All @@ -28,21 +27,14 @@ import kotlin.reflect.full.memberFunctions
* Uses reflection to create a copy with any constructor parameter which matches the given [paramToValue] map.
* It is recommend to only use data classes.
*
* @param paramToValue <PropertyName, value> to match the constructor parameters
* @return a copy of the class or this if an error occurs
*
* @throws [IllegalArgumentException] if the copied types do not match
* @throws [IllegalArgumentException] if the copied types do not match.
* @throws [NoSuchElementException] if no copy method was found for the class.
*/
@Suppress("UNCHECKED_CAST")
internal fun <T : Any> T.copy(paramToValue: Map<String, Any?> = emptyMap()): T {
val instanceClass = this::class

val copyFunction = instanceClass::memberFunctions.get().firstOrNull { it.name == "copy" }
if (copyFunction == null) {
Log.w(instanceClass.TAG, "No copy function found for class: $instanceClass")
return this
}

val copyFunction = instanceClass::memberFunctions.get().first { it.name == "copy" }
val instanceParameter = copyFunction.instanceParameter ?: return this

val valueArgs = copyFunction.parameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,64 +23,98 @@ import android.util.Log
import org.eclipse.kuksa.proto.v1.Types
import org.eclipse.kuksa.proto.v1.Types.BoolArray
import org.eclipse.kuksa.proto.v1.Types.Datapoint
import org.eclipse.kuksa.proto.v1.Types.Datapoint.ValueCase
import org.eclipse.kuksa.vsscore.model.VssProperty

private const val CSV_DELIMITER = ","

val Types.Metadata.valueType: Datapoint.ValueCase
/**
* Returns the converted VSS value types -> Protobuf data types.
*/
val Types.Metadata.valueType: ValueCase
get() = dataType.dataPointValueCase

fun Datapoint.ValueCase.createDatapoint(value: String): Datapoint {
/**
* Converts the [VssProperty.value] into a [Datapoint] object.
*
* @throws IllegalArgumentException if the [VssProperty] could not be converted to a [Datapoint].
*/
val <T : Any> VssProperty<T>.datapoint: Datapoint
get() {
val stringValue = value.toString()
return when (value::class) {
String::class -> ValueCase.STRING.createDatapoint(stringValue)
Boolean::class -> ValueCase.BOOL.createDatapoint(stringValue)
Float::class -> ValueCase.FLOAT.createDatapoint(stringValue)
Double::class -> ValueCase.DOUBLE.createDatapoint(stringValue)
Int::class -> ValueCase.INT32.createDatapoint(stringValue)
Long::class -> ValueCase.INT64.createDatapoint(stringValue)
UInt::class -> ValueCase.UINT32.createDatapoint(stringValue)
Array<String>::class -> ValueCase.DOUBLE.createDatapoint(stringValue)
IntArray::class -> ValueCase.INT32_ARRAY.createDatapoint(stringValue)
BooleanArray::class -> ValueCase.BOOL_ARRAY.createDatapoint(stringValue)
LongArray::class -> ValueCase.INT64_ARRAY.createDatapoint(stringValue)

else -> throw IllegalArgumentException("Could not create datapoint for the value class: ${value::class}!")
Chrylo marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* Creates a [Datapoint] object with a given [value] which is in [String] format. The [String] will be converted
* to the correct type for the [Datapoint.Builder].
*/
fun ValueCase.createDatapoint(value: String): Datapoint {
val datapointBuilder = Datapoint.newBuilder()

try {
when (this) {
Datapoint.ValueCase.VALUE_NOT_SET, // also explicitly handled on UI level
Datapoint.ValueCase.STRING,
ValueCase.VALUE_NOT_SET, // also explicitly handled on UI level
ValueCase.STRING,
-> datapointBuilder.string = value

Datapoint.ValueCase.UINT32 ->
ValueCase.UINT32 ->
datapointBuilder.uint32 = value.toInt()

Datapoint.ValueCase.INT32 ->
ValueCase.INT32 ->
datapointBuilder.int32 = value.toInt()

Datapoint.ValueCase.UINT64 ->
ValueCase.UINT64 ->
datapointBuilder.uint64 = value.toLong()

Datapoint.ValueCase.INT64 ->
ValueCase.INT64 ->
datapointBuilder.int64 = value.toLong()

Datapoint.ValueCase.FLOAT ->
ValueCase.FLOAT ->
datapointBuilder.float = value.toFloat()

Datapoint.ValueCase.DOUBLE ->
ValueCase.DOUBLE ->
datapointBuilder.double = value.toDouble()

Datapoint.ValueCase.BOOL ->
ValueCase.BOOL ->
datapointBuilder.bool = value.toBoolean()

Datapoint.ValueCase.STRING_ARRAY ->
ValueCase.STRING_ARRAY ->
datapointBuilder.stringArray = createStringArray(value)

Datapoint.ValueCase.UINT32_ARRAY ->
ValueCase.UINT32_ARRAY ->
datapointBuilder.uint32Array = createUInt32Array(value)

Datapoint.ValueCase.INT32_ARRAY ->
ValueCase.INT32_ARRAY ->
datapointBuilder.int32Array = createInt32Array(value)

Datapoint.ValueCase.UINT64_ARRAY ->
ValueCase.UINT64_ARRAY ->
datapointBuilder.uint64Array = createUInt64Array(value)

Datapoint.ValueCase.INT64_ARRAY ->
ValueCase.INT64_ARRAY ->
datapointBuilder.int64Array = createInt64Array(value)

Datapoint.ValueCase.FLOAT_ARRAY ->
ValueCase.FLOAT_ARRAY ->
datapointBuilder.floatArray = createFloatArray(value)

Datapoint.ValueCase.DOUBLE_ARRAY ->
ValueCase.DOUBLE_ARRAY ->
datapointBuilder.doubleArray = createDoubleArray(value)

Datapoint.ValueCase.BOOL_ARRAY ->
ValueCase.BOOL_ARRAY ->
datapointBuilder.boolArray = createBoolArray(value)
}
} catch (e: NumberFormatException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import org.eclipse.kuksa.proto.v1.Types
import org.eclipse.kuksa.proto.v1.Types.Datapoint.ValueCase

/**
* Returns the converted VSS data types -> Protobuf data types
* Returns the converted VSS data types -> Protobuf data types.
*/
val Types.DataType.dataPointValueCase: ValueCase
get() {
Expand Down
Loading