Skip to content

Commit

Permalink
feature: Add update API for generated specifications
Browse files Browse the repository at this point in the history
closes #23

Signed-Off-By: Mark Hüsers <mark.huesers@etas.com>
  • Loading branch information
Chrylo committed Oct 20, 2023
1 parent 3ea86a6 commit 9f150e8
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 43 deletions.
36 changes: 25 additions & 11 deletions kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ 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.createProperties
import org.eclipse.kuksa.extension.createPropertyDataPoints
import org.eclipse.kuksa.model.Property
import org.eclipse.kuksa.pattern.listener.MultiListener
import org.eclipse.kuksa.proto.v1.KuksaValV1
Expand Down Expand Up @@ -80,7 +82,7 @@ class DataBrokerConnection internal constructor(
* @throws DataBrokerException in case the connection to the DataBroker is no longer active
*/
fun subscribe(
properties: List<Property>,
properties: Collection<Property>,
propertyObserver: PropertyObserver,
) {
val asyncStub = VALGrpc.newStub(managedChannel)
Expand Down Expand Up @@ -141,14 +143,7 @@ class DataBrokerConnection internal constructor(
fields: List<Types.Field> = listOf(Types.Field.FIELD_VALUE),
observer: VssSpecificationObserver<T>,
) {
val vssPathToVssProperty = specification.heritage
.ifEmpty { setOf(specification) }
.filterIsInstance<VssProperty<*>>() // Only final leafs with a value can be observed
.groupBy { it.vssPath }
.mapValues { it.value.first() } // Always one result because the vssPath is unique
val leafProperties = vssPathToVssProperty.values
.map { Property(it.vssPath, fields) }
.toList()
val leafProperties = specification.createProperties(fields)

try {
Log.d(TAG, "Subscribing to the following properties: $leafProperties")
Expand Down Expand Up @@ -251,8 +246,7 @@ class DataBrokerConnection internal constructor(
}

/**
* Updates the underlying property of the specified vssPath with the updatedProperty. Notifies the callback
* about (un)successful operation.
* Updates the underlying property of the specified vssPath with the updated property.
*
* @throws DataBrokerException in case the connection to the DataBroker is no longer active
*/
Expand Down Expand Up @@ -283,6 +277,26 @@ class DataBrokerConnection internal constructor(
}
}

/**
* 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
*/
suspend fun update(vssSpecification: VssSpecification): List<SetResponse> {
val responses = mutableListOf<SetResponse>()

val propertyDataPointPairs = vssSpecification.createPropertyDataPoints()
propertyDataPointPairs.forEach { (property, dataPoint) ->
val response = update(property, dataPoint)
responses.add(response)
}

return responses
}

/**
* Disconnect from the DataBroker.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,64 +23,96 @@ 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.
*/
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}!")
}
}

/**
* 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.extension

import org.eclipse.kuksa.model.Property
import org.eclipse.kuksa.proto.v1.Types
import org.eclipse.kuksa.proto.v1.Types.Datapoint
import org.eclipse.kuksa.vsscore.model.VssProperty
import org.eclipse.kuksa.vsscore.model.VssSpecification
import org.eclipse.kuksa.vsscore.model.latestGeneration

/**
* Finds all [VssProperty] heirs for the [VssSpecification] and converts them into a collection of [Pair] with a
* [Property] and [Datapoint].
*/
fun VssSpecification.createPropertyDataPoints(
fields: List<Types.Field> = listOf(Types.Field.FIELD_VALUE),
): Collection<Pair<Property, Datapoint>> {
return latestGeneration
.map { vssProperty ->
val property = Property(vssProperty.vssPath, fields)
val datapoint = vssProperty.datapoint
Pair(property, datapoint)
}
}

/**
* Finds all [VssProperty] heirs for the [VssSpecification] and converts them into a collection of [Property].
*/
fun VssSpecification.createProperties(
fields: List<Types.Field> = listOf(Types.Field.FIELD_VALUE),
): Collection<Property> {
return latestGeneration
.map { Property(it.vssPath, fields) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ data class Property(
/**
* The corresponding field type of the Property. The default is [FIELD_VALUE].
*/
val fields: List<Field> = listOf(FIELD_VALUE),
val fields: Collection<Field> = listOf(FIELD_VALUE),
)
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,16 @@ val VssSpecification.parentKey: String
/**
* Iterates through all nested children which also may have children and aggregates them into one big collection.
*/
val VssSpecification.heritage: List<VssSpecification>
val VssSpecification.heritage: Collection<VssSpecification>
get() = children.toList() + children.flatMap { it.heritage }

/**
* Creates an inheritance line to the given heir. Similar to [vssPathHeritageLine] but the other way around.
*
* @param heir where the inheritance line should stop
* @return a [Collection] of the full heritage line in the form of [VssSpecification]
* Finds the latest generation in the form of [VssProperty] for the current [VssSpecification].
*/
fun VssSpecification.findHeritageLine(heir: VssSpecification): List<VssSpecification> {
val specificationKeys = heir.vssPathHeritageLine
return heritage.filter { child ->
specificationKeys.contains(child.vssPath)
}
}
val VssSpecification.latestGeneration: Collection<VssProperty<*>>
get() = heritage
.ifEmpty { setOf(this) }
.filterIsInstance<VssProperty<*>>()

/**
* Uses the [variablePrefix] to generate a unique variable name. The first character is at least lowercased.
Expand Down Expand Up @@ -160,3 +155,16 @@ private val VssSpecification.isVariableOccupied: Boolean

private val classNamePrefix: String
get() = "Vss"

/**
* Creates an inheritance line to the given heir. Similar to [vssPathHeritageLine] but the other way around.
*
* @param heir where the inheritance line should stop
* @return a [Collection] of the full heritage line in the form of [VssSpecification]
*/
fun VssSpecification.findHeritageLine(heir: VssSpecification): List<VssSpecification> {
val specificationKeys = heir.vssPathHeritageLine
return heritage.filter { child ->
specificationKeys.contains(child.vssPath)
}
}

0 comments on commit 9f150e8

Please sign in to comment.