hub returns list of messages.

This commit is contained in:
Alexander Nozik 2023-12-28 21:09:23 +03:00
parent 34f9108ef7
commit aa52b4b927
9 changed files with 59 additions and 35 deletions

View File

@ -10,6 +10,7 @@
### Changed
- Property caching moved from core `Device` to the `CachingDevice`
- `DeviceSpec` properties no explicitly pass property name to getters and setters.
- `DeviceHub.respondHubMessage` now returns a list of messages to allow querying multiple devices. Device server also returns an array.
### Deprecated

View File

@ -14,22 +14,27 @@ public interface DeviceHub : Provider {
override val defaultChainTarget: String get() = Device.DEVICE_TARGET
override fun content(target: String): Map<Name, Any> = if (target == Device.DEVICE_TARGET) {
buildMap {
fun putAll(prefix: Name, hub: DeviceHub) {
hub.devices.forEach {
put(prefix + it.key, it.value)
}
}
devices.forEach {
val name = it.key.asName()
put(name, it.value)
(it.value as? DeviceHub)?.let { hub ->
putAll(name, hub)
}
/**
* List all devices, including sub-devices
*/
public fun buildDeviceTree(): Map<Name, Device> = buildMap {
fun putAll(prefix: Name, hub: DeviceHub) {
hub.devices.forEach {
put(prefix + it.key, it.value)
}
}
devices.forEach {
val name = it.key.asName()
put(name, it.value)
(it.value as? DeviceHub)?.let { hub ->
putAll(name, hub)
}
}
}
override fun content(target: String): Map<Name, Any> = if (target == Device.DEVICE_TARGET) {
buildDeviceTree()
} else {
emptyMap()
}
@ -37,6 +42,7 @@ public interface DeviceHub : Provider {
public companion object
}
public operator fun DeviceHub.get(nameToken: NameToken): Device =
devices[nameToken] ?: error("Device with name $nameToken not found in $this")

View File

@ -103,7 +103,7 @@ public data class PropertyGetMessage(
@SerialName("description.get")
public data class GetDescriptionMessage(
override val sourceDevice: Name? = null,
override val targetDevice: Name,
override val targetDevice: Name? = null,
override val comment: String? = null,
@EncodeDefault override val time: Instant? = Clock.System.now(),
) : DeviceMessage() {

View File

@ -68,15 +68,22 @@ public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMess
}
/**
* Process incoming [DeviceMessage], using hub naming to evaluate target.
* Process incoming [DeviceMessage], using hub naming to find target.
* If the `targetDevice` is `null`, then message is sent to each device in this hub
*/
public suspend fun DeviceHub.respondHubMessage(request: DeviceMessage): DeviceMessage? {
public suspend fun DeviceHub.respondHubMessage(request: DeviceMessage): List<DeviceMessage> {
return try {
val targetName = request.targetDevice ?: return null
val device = getOrNull(targetName) ?: error("The device with name $targetName not found in $this")
device.respondMessage(targetName, request)
val targetName = request.targetDevice
if(targetName == null) {
buildDeviceTree().mapNotNull {
it.value.respondMessage(it.key, request)
}
} else {
val device = getOrNull(targetName) ?: error("The device with name $targetName not found in $this")
listOfNotNull(device.respondMessage(targetName, request))
}
} catch (ex: Exception) {
DeviceMessage.error(ex, sourceDevice = Name.EMPTY, targetDevice = request.sourceDevice)
listOf(DeviceMessage.error(ex, sourceDevice = Name.EMPTY, targetDevice = request.sourceDevice))
}
}

View File

@ -39,10 +39,10 @@ public fun DeviceManager.launchMagixService(
): Job = context.launch {
endpoint.subscribe(controlsMagixFormat, targetFilter = listOf(endpointID)).onEach { (request, payload) ->
val responsePayload = respondHubMessage(payload)
if (responsePayload != null) {
responsePayload.forEach {
endpoint.send(
format = controlsMagixFormat,
payload = responsePayload,
payload = it,
source = endpointID,
target = request.sourceEndpoint,
id = generateId(request),

View File

@ -157,8 +157,8 @@ public fun Application.deviceManagerModule(
val body = call.receiveText()
val request: DeviceMessage = MagixEndpoint.magixJson.decodeFromString(DeviceMessage.serializer(), body)
val response = manager.respondHubMessage(request)
if (response != null) {
call.respondMessage(response)
if (response.isNotEmpty()) {
call.respondMessages(response)
} else {
call.respondText("No response")
}
@ -177,9 +177,9 @@ public fun Application.deviceManagerModule(
property = property,
)
val response = manager.respondHubMessage(request)
if (response != null) {
call.respondMessage(response)
val responses = manager.respondHubMessage(request)
if (responses.isNotEmpty()) {
call.respondMessages(responses)
} else {
call.respond(HttpStatusCode.InternalServerError)
}
@ -197,9 +197,9 @@ public fun Application.deviceManagerModule(
value = json.toMeta()
)
val response = manager.respondHubMessage(request)
if (response != null) {
call.respondMessage(response)
val responses = manager.respondHubMessage(request)
if (responses.isNotEmpty()) {
call.respondMessages(responses)
} else {
call.respond(HttpStatusCode.InternalServerError)
}

View File

@ -5,6 +5,7 @@ import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respondText
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.serializer
import space.kscience.controls.api.DeviceMessage
import space.kscience.magix.api.MagixEndpoint
@ -25,7 +26,7 @@ internal suspend fun ApplicationCall.respondJson(builder: JsonObjectBuilder.() -
respondText(json.toString(), contentType = ContentType.Application.Json)
}
internal suspend fun ApplicationCall.respondMessage(message: DeviceMessage): Unit = respondText(
MagixEndpoint.magixJson.encodeToString(DeviceMessage.serializer(), message),
internal suspend fun ApplicationCall.respondMessages(messages: List<DeviceMessage>): Unit = respondText(
MagixEndpoint.magixJson.encodeToString(serializer<List<DeviceMessage>>(), messages),
contentType = ContentType.Application.Json
)

View File

@ -24,7 +24,7 @@ dependencies {
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("no.tornado:tornadofx:1.7.20")
implementation("space.kscience:plotlykt-server:0.6.0")
implementation("space.kscience:plotlykt-server:0.6.1")
// implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
implementation(spclibs.logback.classic)
}

View File

@ -47,7 +47,12 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<IDemoDevice>(Compa
description = "Real to virtual time scale"
}
val sinScale by mutableProperty(MetaConverter.double, IDemoDevice::sinScaleState)
val sinScale by mutableProperty(MetaConverter.double, IDemoDevice::sinScaleState){
description = "The scale of sin plot"
metaDescriptor {
valueType(ValueType.NUMBER)
}
}
val cosScale by mutableProperty(MetaConverter.double, IDemoDevice::cosScaleState)
val sin by doubleProperty { sinValue() }
@ -74,6 +79,10 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<IDemoDevice>(Compa
write(cosScale, 1.0)
}
val setSinScale by action(MetaConverter.double, MetaConverter.unit){ value: Double ->
write(sinScale, value)
}
override suspend fun IDemoDevice.onOpen() {
launch {
read(sinScale)