Skip to content

Commit

Permalink
Add support for JSON Schema configuration.
Browse files Browse the repository at this point in the history
JSON Schemas are still not in the actual use, but it is configurable at least.
  • Loading branch information
atsushieno committed Mar 5, 2024
1 parent eca4b7d commit 05697ca
Show file tree
Hide file tree
Showing 13 changed files with 95 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ class CIDeviceModel(val parent: CIDeviceManager, val muid: Int, config: MidiCIDe
device.updateDeviceInfo(deviceInfo)
}

fun updateJsonSchemaString(value: String) {
device.config.jsonSchemaString = value
}

val localProperties by device.propertyHost::properties

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import dev.atsushieno.ktmidi.ci.*
import dev.atsushieno.ktmidi.ci.json.Json
import dev.atsushieno.ktmidi.ci.propertycommonrules.PropertyCommonConverter
import dev.atsushieno.ktmidi.ci.propertycommonrules.PropertyResourceNames

class ClientConnectionModel(val parent: CIDeviceModel, val conn: ClientConnection) {
Expand Down Expand Up @@ -81,8 +83,12 @@ class ClientConnectionModel(val parent: CIDeviceModel, val conn: ClientConnectio
properties.removeAt(index)
properties.add(index, entry)
}
if (entry.id == PropertyResourceNames.DEVICE_INFO)
deviceInfo.value = conn.deviceInfo
when (entry.id) {
PropertyResourceNames.DEVICE_INFO ->
deviceInfo.value = conn.deviceInfo
PropertyResourceNames.JSON_SCHEMA ->
conn.jsonSchema = Json.parse(entry.body.toByteArray().decodeToString())
}
}

conn.propertyClient.properties.propertiesCatalogUpdated.add {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package dev.atsushieno.ktmidi.citool.view

import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
Expand All @@ -17,14 +14,16 @@ import dev.atsushieno.ktmidi.ci.MidiCIDeviceInfo

@Composable
fun DeviceInfoItem(label: String) {
Card { Text(label, modifier= Modifier.padding(6.dp, 0.dp).width(150.dp)) }
Card(Modifier.padding(12.dp, 0.dp)) {
Text(label, modifier= Modifier.padding(6.dp, 0.dp).width(150.dp))
}
}

@Composable
private fun NumericTextField(num: Int, onUpdate: (Int) -> Unit) {
TextField(num.toString(16),
{val v = it.toIntOrNull(16); if (v != null) onUpdate(v) },
modifier = Modifier.width(150.dp))
modifier = Modifier.padding(12.dp, 0.dp).width(120.dp))
}

@Composable
Expand All @@ -39,6 +38,7 @@ fun LocalDeviceConfiguration(vm: DeviceConfigurationViewModel) {
val version by remember { vm.version }
val serialNumber by remember { vm.serialNumber }
val maxSimultaneousPropertyRequests by remember { vm.maxSimultaneousPropertyRequests }
val jsonSchemaString by remember { vm.jsonSchemaString }
val update: ((info: MidiCIDeviceInfo) -> Unit) -> Unit = {
val dev = MidiCIDeviceInfo(
manufacturerId, familyId, modelId, versionId,
Expand All @@ -51,32 +51,44 @@ fun LocalDeviceConfiguration(vm: DeviceConfigurationViewModel) {
Text("Local Device Configuration", fontSize = 24.sp, fontWeight = FontWeight.Bold)
Text("Note that each ID byte is in 7 bits. Hex more than 80h is invalid.", fontSize = 12.sp)
Row {
Column(Modifier.border(1.dp, MaterialTheme.colorScheme.primaryContainer)) {
Column {
Row {
DeviceInfoItem("Manufacturer")
Text("ID (3bytes):")
Column {
Text("ID:")
Text("(3bytes)")
}
NumericTextField(manufacturerId) { value -> update { it.manufacturerId = value } }
}
Row {
DeviceInfoItem("Family")
Text("ID (2bytes):")
Column {
Text("ID:")
Text("(2bytes)")
}
NumericTextField(familyId.toInt()) { value -> update { it.familyId = value.toShort() } }
}
Row {
DeviceInfoItem("Model")
Text("ID (2bytes):")
Column {
Text("ID:")
Text("(2bytes)")
}
NumericTextField(modelId.toInt()) { value -> update { it.modelId = value.toShort() } }
}
Row {
DeviceInfoItem("Revision")
Text("ID (4bytes):")
Column {
Text("ID:")
Text("(4bytes)")
}
NumericTextField(versionId) { value -> update { it.versionId = value } }
}
Row {
DeviceInfoItem("SerialNumber")
}
}
Column(Modifier.border(1.dp, MaterialTheme.colorScheme.primaryContainer)) {
Column {
Row {
Text("Text:")
TextField(manufacturer, { value: String -> update { it.manufacturer = value } })
Expand All @@ -101,14 +113,18 @@ fun LocalDeviceConfiguration(vm: DeviceConfigurationViewModel) {
}
Divider()
Row {
DeviceItemCard("max connections")
DeviceInfoItem("max connections")
TextField(
"$maxSimultaneousPropertyRequests",
{ value: String ->
vm.updateMaxSimultaneousPropertyRequests(
value.toByteOrNull() ?: maxSimultaneousPropertyRequests
)
})
{ vm.updateMaxSimultaneousPropertyRequests(it.toByteOrNull() ?: maxSimultaneousPropertyRequests) },
modifier = Modifier.padding(12.dp, 0.dp).width(120.dp))
}
Divider()
Row {
DeviceInfoItem("JSON Schema")
TextField(jsonSchemaString, { vm.updateJsonSchemaString(it) }, minLines = 2,
modifier= Modifier.padding(12.dp, 0.dp).fillMaxWidth())
// FIXME: provide file upload feature here
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class DeviceConfigurationViewModel(private val device: CIDeviceModel, private va

fun updateMaxSimultaneousPropertyRequests(newValue: Byte) {
config.maxSimultaneousPropertyRequests = newValue
maxSimultaneousPropertyRequests.value = newValue
}

var manufacturerId = mutableStateOf(deviceInfo.manufacturerId)
Expand All @@ -245,6 +246,13 @@ class DeviceConfigurationViewModel(private val device: CIDeviceModel, private va
this.version.value = deviceInfo.version
this.serialNumber.value = deviceInfo.serialNumber
}

var jsonSchemaString = mutableStateOf(device.device.config.jsonSchemaString)

fun updateJsonSchemaString(newValue: String) {
device.updateJsonSchemaString(newValue)
jsonSchemaString.value = newValue
}
}

class ApplicationSettingsViewModel(val repository: CIToolRepository, config: MidiCIDeviceConfiguration) {
Expand Down
6 changes: 6 additions & 0 deletions ktmidi-ci/api/android/ktmidi-ci.api
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,14 @@ public final class dev/atsushieno/ktmidi/ci/ClientConnection {
public fun <init> (Ldev/atsushieno/ktmidi/ci/MidiCIDevice;ILdev/atsushieno/ktmidi/ci/DeviceDetails;BLjava/lang/String;)V
public synthetic fun <init> (Ldev/atsushieno/ktmidi/ci/MidiCIDevice;ILdev/atsushieno/ktmidi/ci/DeviceDetails;BLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getDeviceInfo ()Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;
public final fun getJsonSchema ()Ldev/atsushieno/ktmidi/ci/json/Json$JsonValue;
public final fun getMaxSimultaneousPropertyRequests ()B
public final fun getProductInstanceId ()Ljava/lang/String;
public final fun getProfileClient ()Ldev/atsushieno/ktmidi/ci/ProfileClientFacade;
public final fun getPropertyClient ()Ldev/atsushieno/ktmidi/ci/PropertyClientFacade;
public final fun getTargetMUID ()I
public final fun setDeviceInfo (Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;)V
public final fun setJsonSchema (Ldev/atsushieno/ktmidi/ci/json/Json$JsonValue;)V
public final fun setMaxSimultaneousPropertyRequests (B)V
public final fun setProductInstanceId (Ljava/lang/String;)V
}
Expand Down Expand Up @@ -716,6 +718,7 @@ public final class dev/atsushieno/ktmidi/ci/MidiCIDevice {
public final fun sendProfileSpecificData (BILdev/atsushieno/ktmidi/ci/MidiCIProfileId;Ljava/util/List;)V
public final fun setMidiMessageReporter (Ldev/atsushieno/ktmidi/ci/MidiMessageReporter;)V
public final fun updateDeviceInfo (Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;)V
public final fun updateJsonSchema (Ljava/lang/String;)V
}

public final class dev/atsushieno/ktmidi/ci/MidiCIDeviceConfiguration {
Expand All @@ -731,6 +734,7 @@ public final class dev/atsushieno/ktmidi/ci/MidiCIDeviceConfiguration {
public final fun getDeviceInfo ()Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;
public final fun getFunctionBlock ()B
public final fun getGroup ()B
public final fun getJsonSchemaString ()Ljava/lang/String;
public final fun getLocalProfiles ()Ljava/util/List;
public final fun getMaxPropertyChunkSize ()I
public final fun getMaxSimultaneousPropertyRequests ()B
Expand All @@ -754,6 +758,7 @@ public final class dev/atsushieno/ktmidi/ci/MidiCIDeviceConfiguration {
public final fun setDeviceInfo (Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;)V
public final fun setFunctionBlock (B)V
public final fun setGroup (B)V
public final fun setJsonSchemaString (Ljava/lang/String;)V
public final fun setMaxPropertyChunkSize (I)V
public final fun setMaxSimultaneousPropertyRequests (B)V
public final fun setMidiMessageReportChannelControllerMessages (B)V
Expand Down Expand Up @@ -1122,6 +1127,7 @@ public final class dev/atsushieno/ktmidi/ci/PropertyHostFacade {
public final fun shutdownSubscription (ILjava/lang/String;)V
public final fun terminateSubscriptionsToAllSubsctibers (B)V
public final fun updateCommonRulesDeviceInfo (Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;)V
public final fun updateJsonSchema (Ljava/lang/String;)V
public final fun updatePropertyMetadata (Ljava/lang/String;Ldev/atsushieno/ktmidi/ci/PropertyMetadata;)V
}

Expand Down
6 changes: 6 additions & 0 deletions ktmidi-ci/api/jvm/ktmidi-ci.api
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,14 @@ public final class dev/atsushieno/ktmidi/ci/ClientConnection {
public fun <init> (Ldev/atsushieno/ktmidi/ci/MidiCIDevice;ILdev/atsushieno/ktmidi/ci/DeviceDetails;BLjava/lang/String;)V
public synthetic fun <init> (Ldev/atsushieno/ktmidi/ci/MidiCIDevice;ILdev/atsushieno/ktmidi/ci/DeviceDetails;BLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getDeviceInfo ()Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;
public final fun getJsonSchema ()Ldev/atsushieno/ktmidi/ci/json/Json$JsonValue;
public final fun getMaxSimultaneousPropertyRequests ()B
public final fun getProductInstanceId ()Ljava/lang/String;
public final fun getProfileClient ()Ldev/atsushieno/ktmidi/ci/ProfileClientFacade;
public final fun getPropertyClient ()Ldev/atsushieno/ktmidi/ci/PropertyClientFacade;
public final fun getTargetMUID ()I
public final fun setDeviceInfo (Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;)V
public final fun setJsonSchema (Ldev/atsushieno/ktmidi/ci/json/Json$JsonValue;)V
public final fun setMaxSimultaneousPropertyRequests (B)V
public final fun setProductInstanceId (Ljava/lang/String;)V
}
Expand Down Expand Up @@ -716,6 +718,7 @@ public final class dev/atsushieno/ktmidi/ci/MidiCIDevice {
public final fun sendProfileSpecificData (BILdev/atsushieno/ktmidi/ci/MidiCIProfileId;Ljava/util/List;)V
public final fun setMidiMessageReporter (Ldev/atsushieno/ktmidi/ci/MidiMessageReporter;)V
public final fun updateDeviceInfo (Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;)V
public final fun updateJsonSchema (Ljava/lang/String;)V
}

public final class dev/atsushieno/ktmidi/ci/MidiCIDeviceConfiguration {
Expand All @@ -731,6 +734,7 @@ public final class dev/atsushieno/ktmidi/ci/MidiCIDeviceConfiguration {
public final fun getDeviceInfo ()Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;
public final fun getFunctionBlock ()B
public final fun getGroup ()B
public final fun getJsonSchemaString ()Ljava/lang/String;
public final fun getLocalProfiles ()Ljava/util/List;
public final fun getMaxPropertyChunkSize ()I
public final fun getMaxSimultaneousPropertyRequests ()B
Expand All @@ -754,6 +758,7 @@ public final class dev/atsushieno/ktmidi/ci/MidiCIDeviceConfiguration {
public final fun setDeviceInfo (Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;)V
public final fun setFunctionBlock (B)V
public final fun setGroup (B)V
public final fun setJsonSchemaString (Ljava/lang/String;)V
public final fun setMaxPropertyChunkSize (I)V
public final fun setMaxSimultaneousPropertyRequests (B)V
public final fun setMidiMessageReportChannelControllerMessages (B)V
Expand Down Expand Up @@ -1122,6 +1127,7 @@ public final class dev/atsushieno/ktmidi/ci/PropertyHostFacade {
public final fun shutdownSubscription (ILjava/lang/String;)V
public final fun terminateSubscriptionsToAllSubsctibers (B)V
public final fun updateCommonRulesDeviceInfo (Ldev/atsushieno/ktmidi/ci/MidiCIDeviceInfo;)V
public final fun updateJsonSchema (Ljava/lang/String;)V
public final fun updatePropertyMetadata (Ljava/lang/String;Ldev/atsushieno/ktmidi/ci/PropertyMetadata;)V
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.atsushieno.ktmidi.ci

import dev.atsushieno.ktmidi.ci.json.Json
import dev.atsushieno.ktmidi.ci.propertycommonrules.*

enum class SubscriptionActionState {
Expand Down Expand Up @@ -28,6 +29,7 @@ class ClientConnection(
"",
""
)
var jsonSchema: Json.JsonValue? = null

val profileClient = ProfileClientFacade(parent, this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,9 @@ class MidiCIDevice(val muid: Int, val config: MidiCIDeviceConfiguration,
config.deviceInfo = deviceInfo
propertyHost.updateCommonRulesDeviceInfo(deviceInfo)
}

fun updateJsonSchema(stringValue: String) {
config.jsonSchemaString = stringValue
propertyHost.updateJsonSchema(stringValue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class MidiCIDeviceConfiguration {
var deviceInfo = MidiCIDeviceInfo(0x123456,0x1234,0x5678,0x00000001,
"atsushieno", "KtMidi", "KtMidi-CI-Tool", "0.1")

var jsonSchemaString: String = ""

var capabilityInquirySupported: Byte = MidiCISupportedCategories.THREE_P
var receivableMaxSysExSize: Int = MidiCIConstants.DEFAULT_RECEIVABLE_MAX_SYSEX_SIZE
var maxSimultaneousPropertyRequests: Byte = MidiCIConstants.DEFAULT_MAX_SIMULTANEOUS_PROPERTY_REQUESTS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ class PropertyHostFacade(private val device: MidiCIDevice) {
p.deviceInfo = deviceInfo
}

fun updateJsonSchema(stringValue: String) {
val p = propertyService
if (p is CommonRulesPropertyService)
p.jsonSchemaString = stringValue
}

// These members were moved from `PropertyExchangeResponder` and might be still unsorted.

private val muid by device::muid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class CommonRulesPropertyClient(private val device: MidiCIDevice, private val co
json.getObjectValue(DeviceInfoPropertyNames.SERIAL_NUMBER)?.stringValue ?: "",
)
}

PropertyResourceNames.JSON_SCHEMA ->
conn.jsonSchema = convertApplicationJsonBytesToJson(data)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import kotlin.random.Random
private val defaultPropertyList = listOf(
CommonRulesPropertyMetadata(PropertyResourceNames.DEVICE_INFO).apply { originator = CommonRulesPropertyMetadata.Originator.SYSTEM },
//PropertyResource(PropertyResourceNames.CHANNEL_LIST),
//PropertyResource(PropertyResourceNames.JSON_SCHEMA)
CommonRulesPropertyMetadata(PropertyResourceNames.JSON_SCHEMA).apply { originator = CommonRulesPropertyMetadata.Originator.SYSTEM }
)


Expand Down Expand Up @@ -56,14 +56,18 @@ class CommonRulesPropertyService(private val device: MidiCIDevice)
get() = device.config.deviceInfo
@JvmName("set_deviceInfo")
set(value) { device.config.deviceInfo = value }
internal var jsonSchemaString
@JvmName("get_jsonSchemaString")
get() = device.config.jsonSchemaString
@JvmName("set_jsonSchemaString")
set(value) { device.config.jsonSchemaString = value }
private val values
@JvmName("get_propertyValues")
get() = device.config.propertyValues
private val metadataList
@JvmName("get_metadataList")
get() = device.config.propertyMetadataList
private val channelList: Json.JsonValue? = null
private val jsonSchema: Json.JsonValue? = null

// MidiCIPropertyService implementation
override val subscriptions = mutableListOf<SubscriptionEntry>()
Expand Down Expand Up @@ -219,7 +223,7 @@ class CommonRulesPropertyService(private val device: MidiCIDevice)
PropertyResourceNames.RESOURCE_LIST -> Json.JsonValue(getMetadataList().map { (it as CommonRulesPropertyMetadata).toJsonValue() })
PropertyResourceNames.DEVICE_INFO -> getDeviceInfoJson()
PropertyResourceNames.CHANNEL_LIST -> channelList
PropertyResourceNames.JSON_SCHEMA -> jsonSchema
PropertyResourceNames.JSON_SCHEMA -> if (device.config.jsonSchemaString.isNotBlank()) Json.parse(device.config.jsonSchemaString) else null
else -> {
val bytes = linkedResources[header.resId] ?: values.firstOrNull { it.id == header.resource }?.body
?: throw PropertyExchangeException("Unknown property: ${header.resource} (resId: ${header.resId}")
Expand Down Expand Up @@ -259,14 +263,8 @@ class CommonRulesPropertyService(private val device: MidiCIDevice)

fun setPropertyData(headerJson: Json.JsonValue, body: List<Byte>): Result<Json.JsonValue> {
val header = getPropertyHeader(headerJson)
when (header.resource) {
PropertyResourceNames.RESOURCE_LIST ->
return Result.failure(PropertyExchangeException("Property is readonly: ${PropertyResourceNames.RESOURCE_LIST}"))
PropertyResourceNames.JSON_SCHEMA ->
return Result.failure(PropertyExchangeException("Property is readonly: ${PropertyResourceNames.JSON_SCHEMA}"))
PropertyResourceNames.CHANNEL_LIST ->
return Result.failure(PropertyExchangeException("Property is readonly: ${PropertyResourceNames.CHANNEL_LIST}"))
}
if (defaultPropertyList.any { it.propertyId == header.resource })
return Result.failure(PropertyExchangeException("Resource is readonly: ${header.resource}"))

val decodedBody = decodeBody(header.mutualEncoding, body)
// Perform partial updates, if applicable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class PropertyFacadesTest {
// test get property
val conn = device1.connections[device2.muid]
assertNotNull(conn)
// It should contain `DeviceInfo` and `X-01` (may increase along with other predefined resources)
assertEquals(2, conn.propertyClient.properties.getMetadataList()!!.size, "client MetadataList size")
// It should contain `DeviceInfo`, `JSONSchema`, and `X-01` (may increase along with other predefined resources)
assertEquals(3, conn.propertyClient.properties.getMetadataList()!!.size, "client MetadataList size")

val client = conn.propertyClient

Expand Down

0 comments on commit 05697ca

Please sign in to comment.