Documentation update and minor refactoring
This commit is contained in:
parent
405bcd6ba3
commit
fcabd9aed4
@ -6,6 +6,7 @@ import kotlinx.coroutines.newCoroutineContext
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import space.kscience.controls.api.*
|
import space.kscience.controls.api.*
|
||||||
|
import space.kscience.controls.manager.DeviceManager
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
@ -105,8 +106,8 @@ public class DeviceClient(
|
|||||||
* Connect to a remote device via this client.
|
* Connect to a remote device via this client.
|
||||||
*/
|
*/
|
||||||
public fun MagixEndpoint.remoteDevice(context: Context, magixTarget: String, deviceName: Name): DeviceClient {
|
public fun MagixEndpoint.remoteDevice(context: Context, magixTarget: String, deviceName: Name): DeviceClient {
|
||||||
val subscription = subscribe(controlsMagixFormat, originFilter = listOf(magixTarget)).map { it.second }
|
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(magixTarget)).map { it.second }
|
||||||
return DeviceClient(context, deviceName, subscription) {
|
return DeviceClient(context, deviceName, subscription) {
|
||||||
broadcast(controlsMagixFormat, it, magixTarget, id = stringUID())
|
broadcast(DeviceManager.magixFormat, it, magixTarget, id = stringUID())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,15 +14,20 @@ import space.kscience.dataforge.context.logger
|
|||||||
import space.kscience.magix.api.*
|
import space.kscience.magix.api.*
|
||||||
|
|
||||||
|
|
||||||
public val controlsMagixFormat: MagixFormat<DeviceMessage> = MagixFormat(
|
internal val controlsMagixFormat: MagixFormat<DeviceMessage> = MagixFormat(
|
||||||
DeviceMessage.serializer(),
|
DeviceMessage.serializer(),
|
||||||
setOf("controls-kt", "dataforge")
|
setOf("controls-kt", "dataforge")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A magix message format to work with Controls-kt data
|
||||||
|
*/
|
||||||
|
public val DeviceManager.Companion.magixFormat: MagixFormat<DeviceMessage> get() = controlsMagixFormat
|
||||||
|
|
||||||
internal fun generateId(request: MagixMessage): String = if (request.id != null) {
|
internal fun generateId(request: MagixMessage): String = if (request.id != null) {
|
||||||
"${request.id}.response"
|
"${request.id}.response"
|
||||||
} else {
|
} else {
|
||||||
"df[${request.payload.hashCode()}"
|
"controls[${request.payload.hashCode().toString(16)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +42,7 @@ public fun DeviceManager.connectToMagix(
|
|||||||
if (responsePayload != null) {
|
if (responsePayload != null) {
|
||||||
endpoint.broadcast(
|
endpoint.broadcast(
|
||||||
format = controlsMagixFormat,
|
format = controlsMagixFormat,
|
||||||
|
target = request.origin,
|
||||||
origin = endpointID,
|
origin = endpointID,
|
||||||
payload = responsePayload,
|
payload = responsePayload,
|
||||||
id = generateId(request),
|
id = generateId(request),
|
||||||
|
@ -7,7 +7,8 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.html.div
|
import kotlinx.html.div
|
||||||
import kotlinx.html.link
|
import kotlinx.html.link
|
||||||
import space.kscience.controls.api.PropertyChangedMessage
|
import space.kscience.controls.api.PropertyChangedMessage
|
||||||
import space.kscience.controls.client.controlsMagixFormat
|
import space.kscience.controls.client.magixFormat
|
||||||
|
import space.kscience.controls.manager.DeviceManager
|
||||||
import space.kscience.controls.spec.name
|
import space.kscience.controls.spec.name
|
||||||
import space.kscience.dataforge.meta.double
|
import space.kscience.dataforge.meta.double
|
||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
@ -54,7 +55,7 @@ suspend fun Trace.updateXYFrom(flow: Flow<Iterable<Pair<Double, Double>>>) {
|
|||||||
|
|
||||||
fun CoroutineScope.startDemoDeviceServer(magixEndpoint: MagixEndpoint): ApplicationEngine {
|
fun CoroutineScope.startDemoDeviceServer(magixEndpoint: MagixEndpoint): ApplicationEngine {
|
||||||
//share subscription to a parse message only once
|
//share subscription to a parse message only once
|
||||||
val subscription = magixEndpoint.subscribe(controlsMagixFormat).shareIn(this, SharingStarted.Lazily)
|
val subscription = magixEndpoint.subscribe(DeviceManager.magixFormat).shareIn(this, SharingStarted.Lazily)
|
||||||
|
|
||||||
val sinFlow = subscription.mapNotNull { (_, payload) ->
|
val sinFlow = subscription.mapNotNull { (_, payload) ->
|
||||||
(payload as? PropertyChangedMessage)?.takeIf { it.property == DemoDevice.sin.name }
|
(payload as? PropertyChangedMessage)?.takeIf { it.property == DemoDevice.sin.name }
|
||||||
|
@ -2,7 +2,8 @@ package space.kscience.controls.demo.car
|
|||||||
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import space.kscience.controls.api.PropertyChangedMessage
|
import space.kscience.controls.api.PropertyChangedMessage
|
||||||
import space.kscience.controls.client.controlsMagixFormat
|
import space.kscience.controls.client.magixFormat
|
||||||
|
import space.kscience.controls.manager.DeviceManager
|
||||||
import space.kscience.controls.spec.write
|
import space.kscience.controls.spec.write
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.Factory
|
import space.kscience.dataforge.context.Factory
|
||||||
@ -18,7 +19,7 @@ import kotlin.time.ExperimentalTime
|
|||||||
class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta) {
|
class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta) {
|
||||||
|
|
||||||
private fun MagixEndpoint.launchMagixVirtualCarUpdate() = launch {
|
private fun MagixEndpoint.launchMagixVirtualCarUpdate() = launch {
|
||||||
subscribe(controlsMagixFormat).collect { (_, payload) ->
|
subscribe(DeviceManager.magixFormat).collect { (_, payload) ->
|
||||||
(payload as? PropertyChangedMessage)?.let { message ->
|
(payload as? PropertyChangedMessage)?.let { message ->
|
||||||
if (message.sourceDevice == Name.parse("virtual-car")) {
|
if (message.sourceDevice == Name.parse("virtual-car")) {
|
||||||
when (message.property) {
|
when (message.property) {
|
||||||
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.isActive
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import space.kscience.controls.client.connectToMagix
|
import space.kscience.controls.client.connectToMagix
|
||||||
import space.kscience.controls.client.controlsMagixFormat
|
import space.kscience.controls.client.magixFormat
|
||||||
import space.kscience.controls.manager.DeviceManager
|
import space.kscience.controls.manager.DeviceManager
|
||||||
import space.kscience.controls.manager.install
|
import space.kscience.controls.manager.install
|
||||||
import space.kscience.controls.spec.*
|
import space.kscience.controls.spec.*
|
||||||
@ -96,7 +96,7 @@ fun main() {
|
|||||||
|
|
||||||
val latest = ConcurrentHashMap<String, Duration>()
|
val latest = ConcurrentHashMap<String, Duration>()
|
||||||
|
|
||||||
monitorEndpoint.subscribe(controlsMagixFormat).onEach { (magixMessage, payload) ->
|
monitorEndpoint.subscribe(DeviceManager.magixFormat).onEach { (magixMessage, payload) ->
|
||||||
latest[magixMessage.origin] = Clock.System.now() - payload.time!!
|
latest[magixMessage.origin] = Clock.System.now() - payload.time!!
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
|
@ -6,6 +6,11 @@ import kotlinx.serialization.KSerializer
|
|||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import space.kscience.magix.api.MagixEndpoint.Companion.magixJson
|
import space.kscience.magix.api.MagixEndpoint.Companion.magixJson
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A format for [MagixMessage] that allows to decode typed payload
|
||||||
|
*
|
||||||
|
* @param formats allowed values of the format field that are processed. The first value is the primary format for sending.
|
||||||
|
*/
|
||||||
public data class MagixFormat<T>(
|
public data class MagixFormat<T>(
|
||||||
val serializer: KSerializer<T>,
|
val serializer: KSerializer<T>,
|
||||||
val formats: Set<String>,
|
val formats: Set<String>,
|
||||||
@ -13,10 +18,15 @@ public data class MagixFormat<T>(
|
|||||||
val defaultFormat: String get() = formats.firstOrNull() ?: "magix"
|
val defaultFormat: String get() = formats.firstOrNull() ?: "magix"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe for messages in given endpoint using [format] to declare format filter as well as automatic decoding.
|
||||||
|
*
|
||||||
|
* @return a flow of pairs (raw message, decoded message). Raw messages are to be used to extract headers.
|
||||||
|
*/
|
||||||
public fun <T> MagixEndpoint.subscribe(
|
public fun <T> MagixEndpoint.subscribe(
|
||||||
format: MagixFormat<T>,
|
format: MagixFormat<T>,
|
||||||
originFilter: Collection<String?>? = null,
|
originFilter: Collection<String>? = null,
|
||||||
targetFilter: Collection<String?>? = null,
|
targetFilter: Collection<String>? = null,
|
||||||
): Flow<Pair<MagixMessage, T>> = subscribe(
|
): Flow<Pair<MagixMessage, T>> = subscribe(
|
||||||
MagixMessageFilter(format = format.formats, origin = originFilter, target = targetFilter)
|
MagixMessageFilter(format = format.formats, origin = originFilter, target = targetFilter)
|
||||||
).map {
|
).map {
|
||||||
@ -24,6 +34,10 @@ public fun <T> MagixEndpoint.subscribe(
|
|||||||
it to value
|
it to value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message using given [format] to encode the message payload. The format field is also taken from [format].
|
||||||
|
*
|
||||||
|
*/
|
||||||
public suspend fun <T> MagixEndpoint.broadcast(
|
public suspend fun <T> MagixEndpoint.broadcast(
|
||||||
format: MagixFormat<T>,
|
format: MagixFormat<T>,
|
||||||
payload: T,
|
payload: T,
|
||||||
|
@ -4,11 +4,14 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that allows receiving only messages with format, origin and target in given list.
|
||||||
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class MagixMessageFilter(
|
public data class MagixMessageFilter(
|
||||||
val format: Collection<String?>? = null,
|
val format: Collection<String>? = null,
|
||||||
val origin: Collection<String?>? = null,
|
val origin: Collection<String>? = null,
|
||||||
val target: Collection<String?>? = null,
|
val target: Collection<String>? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
public fun accepts(message: MagixMessage): Boolean =
|
public fun accepts(message: MagixMessage): Boolean =
|
||||||
|
@ -77,6 +77,7 @@ public class MqttMagixEndpoint(
|
|||||||
public companion object {
|
public companion object {
|
||||||
public const val DEFAULT_MAGIX_TOPIC_NAME: String = "magix"
|
public const val DEFAULT_MAGIX_TOPIC_NAME: String = "magix"
|
||||||
|
|
||||||
|
//TODO add target name escaping
|
||||||
|
|
||||||
internal val defaultBroadcastTopicBuilder: (MagixMessage) -> String = { message ->
|
internal val defaultBroadcastTopicBuilder: (MagixMessage) -> String = { message ->
|
||||||
message.target?.let { "$DEFAULT_MAGIX_TOPIC_NAME/it" } ?: DEFAULT_MAGIX_TOPIC_NAME
|
message.target?.let { "$DEFAULT_MAGIX_TOPIC_NAME/it" } ?: DEFAULT_MAGIX_TOPIC_NAME
|
||||||
|
@ -35,7 +35,8 @@ public class RSocketMagixFlowPlugin(
|
|||||||
receive: Flow<MagixMessage>,
|
receive: Flow<MagixMessage>,
|
||||||
sendMessage: suspend (MagixMessage) -> Unit,
|
sendMessage: suspend (MagixMessage) -> Unit,
|
||||||
): Job {
|
): Job {
|
||||||
val tcpTransport = TcpServerTransport(hostname = serverHost, port = serverPort, configure = transportConfiguration)
|
val tcpTransport =
|
||||||
|
TcpServerTransport(hostname = serverHost, port = serverPort, configure = transportConfiguration)
|
||||||
val rSocketJob: TcpServer = RSocketServer(rsocketConfiguration)
|
val rSocketJob: TcpServer = RSocketServer(rsocketConfiguration)
|
||||||
.bindIn(scope, tcpTransport, acceptor(scope, receive, sendMessage))
|
.bindIn(scope, tcpTransport, acceptor(scope, receive, sendMessage))
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ public class RSocketMagixFlowPlugin(
|
|||||||
MagixMessageFilter.serializer(),
|
MagixMessageFilter.serializer(),
|
||||||
request.data.readText()
|
request.data.readText()
|
||||||
)
|
)
|
||||||
|
request.close()
|
||||||
receive.filter(filter).map { message ->
|
receive.filter(filter).map { message ->
|
||||||
val string = MagixEndpoint.magixJson.encodeToString(MagixMessage.serializer(), message)
|
val string = MagixEndpoint.magixJson.encodeToString(MagixMessage.serializer(), message)
|
||||||
buildPayload { data(string) }
|
buildPayload { data(string) }
|
||||||
@ -66,22 +68,25 @@ public class RSocketMagixFlowPlugin(
|
|||||||
}
|
}
|
||||||
//single send
|
//single send
|
||||||
fireAndForget { request: Payload ->
|
fireAndForget { request: Payload ->
|
||||||
val message =
|
val message = MagixEndpoint.magixJson.decodeFromString(
|
||||||
MagixEndpoint.magixJson.decodeFromString(MagixMessage.serializer(), request.data.readText())
|
MagixMessage.serializer(),
|
||||||
|
request.data.readText()
|
||||||
|
)
|
||||||
|
request.close()
|
||||||
sendMessage(message)
|
sendMessage(message)
|
||||||
}
|
}
|
||||||
// bidirectional connection, used for streaming connection
|
// bidirectional connection, used for streaming connection
|
||||||
requestChannel { request: Payload, input: Flow<Payload> ->
|
requestChannel { request: Payload, input: Flow<Payload> ->
|
||||||
input.onEach {
|
input.onEach { inputPayload ->
|
||||||
sendMessage(
|
sendMessage(
|
||||||
MagixEndpoint.magixJson.decodeFromString(
|
MagixEndpoint.magixJson.decodeFromString(
|
||||||
MagixMessage.serializer(),
|
MagixMessage.serializer(),
|
||||||
it.data.readText()
|
inputPayload.use{ it.data.readText()}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
val filterText = request.data.readText()
|
val filterText = request.use { it.data.readText()}
|
||||||
|
|
||||||
val filter = if (filterText.isNotBlank()) {
|
val filter = if (filterText.isNotBlank()) {
|
||||||
MagixEndpoint.magixJson.decodeFromString(MagixMessageFilter.serializer(), filterText)
|
MagixEndpoint.magixJson.decodeFromString(MagixMessageFilter.serializer(), filterText)
|
||||||
|
@ -47,24 +47,24 @@ public fun Application.magixModule(magixFlow: MutableSharedFlow<MagixMessage>, r
|
|||||||
install(WebSockets)
|
install(WebSockets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pluginOrNull(RSocketSupport) == null) {
|
||||||
|
install(RSocketSupport)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// if (pluginOrNull(CORS) == null) {
|
// if (pluginOrNull(CORS) == null) {
|
||||||
// install(CORS) {
|
// install(CORS) {
|
||||||
// //TODO consider more safe policy
|
// //TODO consider more safe policy
|
||||||
// anyHost()
|
// anyHost()
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
if (pluginOrNull(ContentNegotiation) == null) {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pluginOrNull(RSocketSupport) == null) {
|
|
||||||
install(RSocketSupport)
|
|
||||||
}
|
|
||||||
|
|
||||||
routing {
|
routing {
|
||||||
route(route) {
|
route(route) {
|
||||||
|
install(ContentNegotiation){
|
||||||
|
json()
|
||||||
|
}
|
||||||
get("state") {
|
get("state") {
|
||||||
call.respondHtml {
|
call.respondHtml {
|
||||||
head {
|
head {
|
||||||
|
@ -58,8 +58,7 @@ include(
|
|||||||
":magix:magix-zmq",
|
":magix:magix-zmq",
|
||||||
":magix:magix-rabbit",
|
":magix:magix-rabbit",
|
||||||
":magix:magix-mqtt",
|
":magix:magix-mqtt",
|
||||||
|
":magix:magix-storage",
|
||||||
// ":magix:magix-storage",
|
|
||||||
":magix:magix-storage:magix-storage-xodus",
|
":magix:magix-storage:magix-storage-xodus",
|
||||||
":controls-magix-client",
|
":controls-magix-client",
|
||||||
":demo:all-things",
|
":demo:all-things",
|
||||||
|
Loading…
Reference in New Issue
Block a user