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 ### Changed
- Property caching moved from core `Device` to the `CachingDevice` - Property caching moved from core `Device` to the `CachingDevice`
- `DeviceSpec` properties no explicitly pass property name to getters and setters. - `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 ### Deprecated

View File

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

View File

@ -103,7 +103,7 @@ public data class PropertyGetMessage(
@SerialName("description.get") @SerialName("description.get")
public data class GetDescriptionMessage( public data class GetDescriptionMessage(
override val sourceDevice: Name? = null, override val sourceDevice: Name? = null,
override val targetDevice: Name, override val targetDevice: Name? = null,
override val comment: String? = null, override val comment: String? = null,
@EncodeDefault override val time: Instant? = Clock.System.now(), @EncodeDefault override val time: Instant? = Clock.System.now(),
) : DeviceMessage() { ) : 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 { return try {
val targetName = request.targetDevice ?: return null val targetName = request.targetDevice
val device = getOrNull(targetName) ?: error("The device with name $targetName not found in $this") if(targetName == null) {
device.respondMessage(targetName, request) 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) { } 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 { ): Job = context.launch {
endpoint.subscribe(controlsMagixFormat, targetFilter = listOf(endpointID)).onEach { (request, payload) -> endpoint.subscribe(controlsMagixFormat, targetFilter = listOf(endpointID)).onEach { (request, payload) ->
val responsePayload = respondHubMessage(payload) val responsePayload = respondHubMessage(payload)
if (responsePayload != null) { responsePayload.forEach {
endpoint.send( endpoint.send(
format = controlsMagixFormat, format = controlsMagixFormat,
payload = responsePayload, payload = it,
source = endpointID, source = endpointID,
target = request.sourceEndpoint, target = request.sourceEndpoint,
id = generateId(request), id = generateId(request),

View File

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

View File

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

View File

@ -24,7 +24,7 @@ dependencies {
implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("no.tornado:tornadofx:1.7.20") 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("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
implementation(spclibs.logback.classic) 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" 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 cosScale by mutableProperty(MetaConverter.double, IDemoDevice::cosScaleState)
val sin by doubleProperty { sinValue() } val sin by doubleProperty { sinValue() }
@ -74,6 +79,10 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<IDemoDevice>(Compa
write(cosScale, 1.0) write(cosScale, 1.0)
} }
val setSinScale by action(MetaConverter.double, MetaConverter.unit){ value: Double ->
write(sinScale, value)
}
override suspend fun IDemoDevice.onOpen() { override suspend fun IDemoDevice.onOpen() {
launch { launch {
read(sinScale) read(sinScale)