Migration to DF-0.5 and bug fixes
This commit is contained in:
parent
b068403429
commit
3606bc3a46
@ -7,7 +7,7 @@ This repository contains a prototype of API and simple implementation
|
|||||||
of a slow control system, including a demo.
|
of a slow control system, including a demo.
|
||||||
|
|
||||||
DataForge-control uses some concepts and modules of DataForge,
|
DataForge-control uses some concepts and modules of DataForge,
|
||||||
such as `Meta` (immutable tree-like structure) and `MetaItem` (which
|
such as `Meta` (immutable tree-like structure) and `Meta` (which
|
||||||
includes a scalar value, or a tree of values, easily convertable to/from JSON
|
includes a scalar value, or a tree of values, easily convertable to/from JSON
|
||||||
if needed).
|
if needed).
|
||||||
|
|
||||||
|
@ -2,16 +2,13 @@ plugins {
|
|||||||
id("ru.mipt.npm.gradle.project")
|
id("ru.mipt.npm.gradle.project")
|
||||||
}
|
}
|
||||||
|
|
||||||
val dataforgeVersion: String by extra("0.4.3")
|
val dataforgeVersion: String by extra("0.5.0-dev-7")
|
||||||
val ktorVersion: String by extra(ru.mipt.npm.gradle.KScienceVersions.ktorVersion)
|
val ktorVersion: String by extra(ru.mipt.npm.gradle.KScienceVersions.ktorVersion)
|
||||||
val rsocketVersion by extra("0.12.0")
|
val rsocketVersion by extra("0.13.1")
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "ru.mipt.npm"
|
group = "ru.mipt.npm"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
repositories{
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ksciencePublish {
|
ksciencePublish {
|
||||||
|
@ -3,12 +3,12 @@ package ru.mipt.npm.controls.api
|
|||||||
import io.ktor.utils.io.core.Closeable
|
import io.ktor.utils.io.core.Closeable
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import ru.mipt.npm.controls.api.Device.Companion.DEVICE_TARGET
|
import ru.mipt.npm.controls.api.Device.Companion.DEVICE_TARGET
|
||||||
import space.kscience.dataforge.context.ContextAware
|
import space.kscience.dataforge.context.ContextAware
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
|
||||||
import space.kscience.dataforge.misc.Type
|
import space.kscience.dataforge.misc.Type
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,12 +31,12 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* Read physical state of property and update/push notifications if needed.
|
* Read physical state of property and update/push notifications if needed.
|
||||||
*/
|
*/
|
||||||
public suspend fun readItem(propertyName: String): MetaItem
|
public suspend fun readProperty(propertyName: String): Meta
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the logical state of property or return null if it is invalid
|
* Get the logical state of property or return null if it is invalid
|
||||||
*/
|
*/
|
||||||
public fun getItem(propertyName: String): MetaItem?
|
public fun getProperty(propertyName: String): Meta?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate property (set logical state to invalid)
|
* Invalidate property (set logical state to invalid)
|
||||||
@ -47,18 +47,19 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
|||||||
* Set property [value] for a property with name [propertyName].
|
* Set property [value] for a property with name [propertyName].
|
||||||
* In rare cases could suspend if the [Device] supports command queue and it is full at the moment.
|
* In rare cases could suspend if the [Device] supports command queue and it is full at the moment.
|
||||||
*/
|
*/
|
||||||
public suspend fun writeItem(propertyName: String, value: MetaItem)
|
public suspend fun writeItem(propertyName: String, value: Meta)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [SharedFlow] of property changes
|
* A subscription-based [Flow] of [DeviceMessage] provided by device. The flow is guaranteed to be readable
|
||||||
|
* multiple times
|
||||||
*/
|
*/
|
||||||
public val propertyFlow: SharedFlow<Pair<String, MetaItem>>
|
public val messageFlow: Flow<DeviceMessage>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an action request and suspend caller while request is being processed.
|
* Send an action request and suspend caller while request is being processed.
|
||||||
* Could return null if request does not return a meaningful answer.
|
* Could return null if request does not return a meaningful answer.
|
||||||
*/
|
*/
|
||||||
public suspend fun execute(action: String, argument: MetaItem? = null): MetaItem?
|
public suspend fun execute(action: String, argument: Meta? = null): Meta?
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
cancel("The device is closed")
|
cancel("The device is closed")
|
||||||
@ -73,16 +74,16 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* Get the logical state of property or suspend to read the physical value.
|
* Get the logical state of property or suspend to read the physical value.
|
||||||
*/
|
*/
|
||||||
public suspend fun Device.getOrReadItem(propertyName: String): MetaItem =
|
public suspend fun Device.getOrReadItem(propertyName: String): Meta =
|
||||||
getItem(propertyName) ?: readItem(propertyName)
|
getProperty(propertyName) ?: readProperty(propertyName)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a snapshot of logical state of the device
|
* Get a snapshot of logical state of the device
|
||||||
*/
|
*/
|
||||||
public fun Device.getProperties(): Meta = Meta {
|
public fun Device.getProperties(): Meta = Meta {
|
||||||
for (descriptor in propertyDescriptors) {
|
for (descriptor in propertyDescriptors) {
|
||||||
descriptor.name put getItem(descriptor.name)
|
setMeta(Name.parse(descriptor.name), getProperty(descriptor.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//public suspend fun Device.execute(name: String, meta: Meta?): MetaItem? = execute(name, meta?.let { MetaItemNode(it) })
|
//public suspend fun Device.execute(name: String, meta: Meta?): Meta? = execute(name, meta?.let { MetaNode(it) })
|
@ -1,6 +1,6 @@
|
|||||||
package ru.mipt.npm.controls.api
|
package ru.mipt.npm.controls.api
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.names.*
|
import space.kscience.dataforge.names.*
|
||||||
import space.kscience.dataforge.provider.Provider
|
import space.kscience.dataforge.provider.Provider
|
||||||
|
|
||||||
@ -8,8 +8,6 @@ import space.kscience.dataforge.provider.Provider
|
|||||||
* A hub that could locate multiple devices and redirect actions to them
|
* A hub that could locate multiple devices and redirect actions to them
|
||||||
*/
|
*/
|
||||||
public interface DeviceHub : Provider {
|
public interface DeviceHub : Provider {
|
||||||
public val deviceName: String
|
|
||||||
|
|
||||||
public val devices: Map<NameToken, Device>
|
public val devices: Map<NameToken, Device>
|
||||||
|
|
||||||
override val defaultTarget: String get() = Device.DEVICE_TARGET
|
override val defaultTarget: String get() = Device.DEVICE_TARGET
|
||||||
@ -53,19 +51,19 @@ public fun DeviceHub.getOrNull(name: Name): Device? = when {
|
|||||||
public operator fun DeviceHub.get(name: Name): Device =
|
public operator fun DeviceHub.get(name: Name): Device =
|
||||||
getOrNull(name) ?: error("Device with name $name not found in $this")
|
getOrNull(name) ?: error("Device with name $name not found in $this")
|
||||||
|
|
||||||
public fun DeviceHub.getOrNull(nameString: String): Device? = getOrNull(nameString.toName())
|
public fun DeviceHub.getOrNull(nameString: String): Device? = getOrNull(Name.parse(nameString))
|
||||||
|
|
||||||
public operator fun DeviceHub.get(nameString: String): Device =
|
public operator fun DeviceHub.get(nameString: String): Device =
|
||||||
getOrNull(nameString) ?: error("Device with name $nameString not found in $this")
|
getOrNull(nameString) ?: error("Device with name $nameString not found in $this")
|
||||||
|
|
||||||
public suspend fun DeviceHub.readItem(deviceName: Name, propertyName: String): MetaItem =
|
public suspend fun DeviceHub.readProperty(deviceName: Name, propertyName: String): Meta =
|
||||||
this[deviceName].readItem(propertyName)
|
this[deviceName].readProperty(propertyName)
|
||||||
|
|
||||||
public suspend fun DeviceHub.writeItem(deviceName: Name, propertyName: String, value: MetaItem) {
|
public suspend fun DeviceHub.writeItem(deviceName: Name, propertyName: String, value: Meta) {
|
||||||
this[deviceName].writeItem(propertyName, value)
|
this[deviceName].writeItem(propertyName, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
public suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: MetaItem?): MetaItem? =
|
public suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: Meta?): Meta? =
|
||||||
this[deviceName].execute(command, argument)
|
this[deviceName].execute(command, argument)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,20 +6,27 @@ import kotlinx.serialization.json.Json
|
|||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
import space.kscience.dataforge.io.SimpleEnvelope
|
import space.kscience.dataforge.io.SimpleEnvelope
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.toJson
|
||||||
|
import space.kscience.dataforge.meta.toMeta
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public sealed class DeviceMessage {
|
public sealed class DeviceMessage {
|
||||||
public abstract val sourceDevice: String?
|
public abstract val sourceDevice: Name?
|
||||||
public abstract val targetDevice: String?
|
public abstract val targetDevice: Name?
|
||||||
public abstract val comment: String?
|
public abstract val comment: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the source device name for composition. If the original name is null, resulting name is also null.
|
||||||
|
*/
|
||||||
|
public abstract fun changeSource(block: (Name) -> Name): DeviceMessage
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public fun error(
|
public fun error(
|
||||||
cause: Throwable,
|
cause: Throwable,
|
||||||
sourceDevice: String,
|
sourceDevice: Name,
|
||||||
targetDevice: String? = null,
|
targetDevice: Name? = null,
|
||||||
): DeviceErrorMessage = DeviceErrorMessage(
|
): DeviceErrorMessage = DeviceErrorMessage(
|
||||||
errorMessage = cause.message,
|
errorMessage = cause.message,
|
||||||
errorType = cause::class.simpleName,
|
errorType = cause::class.simpleName,
|
||||||
@ -42,11 +49,13 @@ public sealed class DeviceMessage {
|
|||||||
@SerialName("property.changed")
|
@SerialName("property.changed")
|
||||||
public data class PropertyChangedMessage(
|
public data class PropertyChangedMessage(
|
||||||
public val property: String,
|
public val property: String,
|
||||||
public val value: MetaItem?,
|
public val value: Meta?,
|
||||||
override val sourceDevice: String,
|
override val sourceDevice: Name = Name.EMPTY,
|
||||||
override val targetDevice: String? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command to set or invalidate property. [targetDevice] is mandatory.
|
* A command to set or invalidate property. [targetDevice] is mandatory.
|
||||||
@ -55,11 +64,13 @@ public data class PropertyChangedMessage(
|
|||||||
@SerialName("property.set")
|
@SerialName("property.set")
|
||||||
public data class PropertySetMessage(
|
public data class PropertySetMessage(
|
||||||
public val property: String,
|
public val property: String,
|
||||||
public val value: MetaItem?,
|
public val value: Meta?,
|
||||||
override val sourceDevice: String? = null,
|
override val sourceDevice: Name? = null,
|
||||||
override val targetDevice: String,
|
override val targetDevice: Name,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command to request property value asynchronously. [targetDevice] is mandatory.
|
* A command to request property value asynchronously. [targetDevice] is mandatory.
|
||||||
@ -69,10 +80,12 @@ public data class PropertySetMessage(
|
|||||||
@SerialName("property.get")
|
@SerialName("property.get")
|
||||||
public data class PropertyGetMessage(
|
public data class PropertyGetMessage(
|
||||||
public val property: String,
|
public val property: String,
|
||||||
override val sourceDevice: String? = null,
|
override val sourceDevice: Name? = null,
|
||||||
override val targetDevice: String,
|
override val targetDevice: Name,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request device description. The result is returned in form of [DescriptionMessage]
|
* Request device description. The result is returned in form of [DescriptionMessage]
|
||||||
@ -80,10 +93,12 @@ public data class PropertyGetMessage(
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("description.get")
|
@SerialName("description.get")
|
||||||
public data class GetDescriptionMessage(
|
public data class GetDescriptionMessage(
|
||||||
override val sourceDevice: String? = null,
|
override val sourceDevice: Name? = null,
|
||||||
override val targetDevice: String,
|
override val targetDevice: Name,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The full device description message
|
* The full device description message
|
||||||
@ -92,10 +107,12 @@ public data class GetDescriptionMessage(
|
|||||||
@SerialName("description")
|
@SerialName("description")
|
||||||
public data class DescriptionMessage(
|
public data class DescriptionMessage(
|
||||||
val description: Meta,
|
val description: Meta,
|
||||||
override val sourceDevice: String,
|
override val sourceDevice: Name,
|
||||||
override val targetDevice: String? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A request to execute an action. [targetDevice] is mandatory
|
* A request to execute an action. [targetDevice] is mandatory
|
||||||
@ -104,11 +121,13 @@ public data class DescriptionMessage(
|
|||||||
@SerialName("action.execute")
|
@SerialName("action.execute")
|
||||||
public data class ActionExecuteMessage(
|
public data class ActionExecuteMessage(
|
||||||
public val action: String,
|
public val action: String,
|
||||||
public val argument: MetaItem?,
|
public val argument: Meta?,
|
||||||
override val sourceDevice: String? = null,
|
override val sourceDevice: Name? = null,
|
||||||
override val targetDevice: String,
|
override val targetDevice: Name,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronous action result. [sourceDevice] is mandatory
|
* Asynchronous action result. [sourceDevice] is mandatory
|
||||||
@ -117,11 +136,13 @@ public data class ActionExecuteMessage(
|
|||||||
@SerialName("action.result")
|
@SerialName("action.result")
|
||||||
public data class ActionResultMessage(
|
public data class ActionResultMessage(
|
||||||
public val action: String,
|
public val action: String,
|
||||||
public val result: MetaItem?,
|
public val result: Meta?,
|
||||||
override val sourceDevice: String,
|
override val sourceDevice: Name,
|
||||||
override val targetDevice: String? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies listeners that a new binary with given [binaryID] is available. The binary itself could not be provided via [DeviceMessage] API.
|
* Notifies listeners that a new binary with given [binaryID] is available. The binary itself could not be provided via [DeviceMessage] API.
|
||||||
@ -130,10 +151,12 @@ public data class ActionResultMessage(
|
|||||||
@SerialName("binary.notification")
|
@SerialName("binary.notification")
|
||||||
public data class BinaryNotificationMessage(
|
public data class BinaryNotificationMessage(
|
||||||
val binaryID: String,
|
val binaryID: String,
|
||||||
override val sourceDevice: String,
|
override val sourceDevice: Name,
|
||||||
override val targetDevice: String? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message states that the message is received, but no meaningful response is produced.
|
* The message states that the message is received, but no meaningful response is produced.
|
||||||
@ -142,10 +165,12 @@ public data class BinaryNotificationMessage(
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("empty")
|
@SerialName("empty")
|
||||||
public data class EmptyDeviceMessage(
|
public data class EmptyDeviceMessage(
|
||||||
override val sourceDevice: String? = null,
|
override val sourceDevice: Name? = null,
|
||||||
override val targetDevice: String? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information log message
|
* Information log message
|
||||||
@ -154,11 +179,13 @@ public data class EmptyDeviceMessage(
|
|||||||
@SerialName("log")
|
@SerialName("log")
|
||||||
public data class DeviceLogMessage(
|
public data class DeviceLogMessage(
|
||||||
val message: String,
|
val message: String,
|
||||||
val data: MetaItem? = null,
|
val data: Meta? = null,
|
||||||
override val sourceDevice: String? = null,
|
override val sourceDevice: Name? = null,
|
||||||
override val targetDevice: String? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The evaluation of the message produced a service error
|
* The evaluation of the message produced a service error
|
||||||
@ -169,12 +196,14 @@ public data class DeviceErrorMessage(
|
|||||||
public val errorMessage: String?,
|
public val errorMessage: String?,
|
||||||
public val errorType: String? = null,
|
public val errorType: String? = null,
|
||||||
public val errorStackTrace: String? = null,
|
public val errorStackTrace: String? = null,
|
||||||
override val sourceDevice: String,
|
override val sourceDevice: Name,
|
||||||
override val targetDevice: String? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
) : DeviceMessage()
|
) : DeviceMessage(){
|
||||||
|
override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun DeviceMessage.toMeta(): JsonMeta = Json.encodeToJsonElement(this).toMetaItem().node!!
|
public fun DeviceMessage.toMeta(): Meta = Json.encodeToJsonElement(this).toMeta()
|
||||||
|
|
||||||
public fun DeviceMessage.toEnvelope(): SimpleEnvelope = SimpleEnvelope(toMeta(), null)
|
public fun DeviceMessage.toEnvelope(): SimpleEnvelope = SimpleEnvelope(toMeta(), null)
|
||||||
|
@ -2,13 +2,9 @@ package ru.mipt.npm.controls.base
|
|||||||
|
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
import ru.mipt.npm.controls.api.ActionDescriptor
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
|
||||||
import space.kscience.dataforge.meta.asMetaItem
|
|
||||||
|
|
||||||
public interface DeviceAction {
|
public interface DeviceAction {
|
||||||
public val name: String
|
public val name: String
|
||||||
public val descriptor: ActionDescriptor
|
public val descriptor: ActionDescriptor
|
||||||
public suspend operator fun invoke(arg: MetaItem? = null): MetaItem?
|
public suspend operator fun invoke(arg: Meta? = null): Meta?
|
||||||
}
|
}
|
||||||
|
|
||||||
public suspend operator fun DeviceAction.invoke(meta: Meta): MetaItem? = invoke(meta.asMetaItem())
|
|
@ -7,12 +7,11 @@ import kotlinx.coroutines.flow.SharedFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
import ru.mipt.npm.controls.api.*
|
||||||
import ru.mipt.npm.controls.api.Device
|
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
|
import kotlin.collections.set
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
//TODO move to DataForge-core
|
//TODO move to DataForge-core
|
||||||
@ -24,28 +23,33 @@ public data class LogEntry(val content: String, val priority: Int = 0)
|
|||||||
private open class BasicReadOnlyDeviceProperty(
|
private open class BasicReadOnlyDeviceProperty(
|
||||||
val device: DeviceBase,
|
val device: DeviceBase,
|
||||||
override val name: String,
|
override val name: String,
|
||||||
default: MetaItem?,
|
default: Meta?,
|
||||||
override val descriptor: PropertyDescriptor,
|
override val descriptor: PropertyDescriptor,
|
||||||
private val getter: suspend (before: MetaItem?) -> MetaItem,
|
private val getter: suspend (before: Meta?) -> Meta,
|
||||||
) : ReadOnlyDeviceProperty {
|
) : ReadOnlyDeviceProperty {
|
||||||
|
|
||||||
override val scope: CoroutineScope get() = device
|
override val scope: CoroutineScope get() = device
|
||||||
|
|
||||||
private val state: MutableStateFlow<MetaItem?> = MutableStateFlow(default)
|
private val state: MutableStateFlow<Meta?> = MutableStateFlow(default)
|
||||||
override val value: MetaItem? get() = state.value
|
override val value: Meta? get() = state.value
|
||||||
|
|
||||||
override suspend fun invalidate() {
|
override suspend fun invalidate() {
|
||||||
state.value = null
|
state.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateLogical(item: MetaItem) {
|
override fun updateLogical(item: Meta) {
|
||||||
state.value = item
|
state.value = item
|
||||||
scope.launch {
|
scope.launch {
|
||||||
device.sharedPropertyFlow.emit(Pair(name, item))
|
device.sharedMessageFlow.emit(
|
||||||
|
PropertyChangedMessage(
|
||||||
|
property = name,
|
||||||
|
value = item,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun read(force: Boolean): MetaItem {
|
override suspend fun read(force: Boolean): Meta {
|
||||||
//backup current value
|
//backup current value
|
||||||
val currentValue = value
|
val currentValue = value
|
||||||
return if (force || currentValue == null) {
|
return if (force || currentValue == null) {
|
||||||
@ -61,7 +65,7 @@ private open class BasicReadOnlyDeviceProperty(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun flow(): StateFlow<MetaItem?> = state
|
override fun flow(): StateFlow<Meta?> = state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -69,13 +73,13 @@ private open class BasicReadOnlyDeviceProperty(
|
|||||||
private class BasicDeviceProperty(
|
private class BasicDeviceProperty(
|
||||||
device: DeviceBase,
|
device: DeviceBase,
|
||||||
name: String,
|
name: String,
|
||||||
default: MetaItem?,
|
default: Meta?,
|
||||||
descriptor: PropertyDescriptor,
|
descriptor: PropertyDescriptor,
|
||||||
getter: suspend (MetaItem?) -> MetaItem,
|
getter: suspend (Meta?) -> Meta,
|
||||||
private val setter: suspend (oldValue: MetaItem?, newValue: MetaItem) -> MetaItem?,
|
private val setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
||||||
) : BasicReadOnlyDeviceProperty(device, name, default, descriptor, getter), DeviceProperty {
|
) : BasicReadOnlyDeviceProperty(device, name, default, descriptor, getter), DeviceProperty {
|
||||||
|
|
||||||
override var value: MetaItem?
|
override var value: Meta?
|
||||||
get() = super.value
|
get() = super.value
|
||||||
set(value) {
|
set(value) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@ -89,7 +93,7 @@ private class BasicDeviceProperty(
|
|||||||
|
|
||||||
private val writeLock = Mutex()
|
private val writeLock = Mutex()
|
||||||
|
|
||||||
override suspend fun write(item: MetaItem) {
|
override suspend fun write(item: Meta) {
|
||||||
writeLock.withLock {
|
writeLock.withLock {
|
||||||
//fast return if value is not changed
|
//fast return if value is not changed
|
||||||
if (item == value) return@withLock
|
if (item == value) return@withLock
|
||||||
@ -113,16 +117,14 @@ public abstract class DeviceBase(final override val context: Context) : Device {
|
|||||||
override val coroutineContext: CoroutineContext =
|
override val coroutineContext: CoroutineContext =
|
||||||
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
||||||
|
|
||||||
|
|
||||||
private val _properties = HashMap<String, ReadOnlyDeviceProperty>()
|
private val _properties = HashMap<String, ReadOnlyDeviceProperty>()
|
||||||
public val properties: Map<String, ReadOnlyDeviceProperty> get() = _properties
|
public val properties: Map<String, ReadOnlyDeviceProperty> get() = _properties
|
||||||
private val _actions = HashMap<String, DeviceAction>()
|
private val _actions = HashMap<String, DeviceAction>()
|
||||||
public val actions: Map<String, DeviceAction> get() = _actions
|
public val actions: Map<String, DeviceAction> get() = _actions
|
||||||
|
|
||||||
internal val sharedPropertyFlow = MutableSharedFlow<Pair<String, MetaItem>>()
|
internal val sharedMessageFlow = MutableSharedFlow<DeviceMessage>()
|
||||||
|
|
||||||
override val propertyFlow: SharedFlow<Pair<String, MetaItem>> get() = sharedPropertyFlow
|
|
||||||
|
|
||||||
|
override val messageFlow: SharedFlow<DeviceMessage> get() = sharedMessageFlow
|
||||||
private val sharedLogFlow = MutableSharedFlow<LogEntry>()
|
private val sharedLogFlow = MutableSharedFlow<LogEntry>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,23 +154,23 @@ public abstract class DeviceBase(final override val context: Context) : Device {
|
|||||||
_actions[name] = action
|
_actions[name] = action
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun readItem(propertyName: String): MetaItem =
|
override suspend fun readProperty(propertyName: String): Meta =
|
||||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).read()
|
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).read()
|
||||||
|
|
||||||
override fun getItem(propertyName: String): MetaItem?=
|
override fun getProperty(propertyName: String): Meta? =
|
||||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).value
|
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).value
|
||||||
|
|
||||||
override suspend fun invalidate(propertyName: String) {
|
override suspend fun invalidate(propertyName: String) {
|
||||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate()
|
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeItem(propertyName: String, value: MetaItem) {
|
override suspend fun writeItem(propertyName: String, value: Meta) {
|
||||||
(_properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write(
|
(_properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write(
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(action: String, argument: MetaItem?): MetaItem? =
|
override suspend fun execute(action: String, argument: Meta?): Meta? =
|
||||||
(_actions[action] ?: error("Request with name $action not defined")).invoke(argument)
|
(_actions[action] ?: error("Request with name $action not defined")).invoke(argument)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,9 +178,9 @@ public abstract class DeviceBase(final override val context: Context) : Device {
|
|||||||
*/
|
*/
|
||||||
public fun createReadOnlyProperty(
|
public fun createReadOnlyProperty(
|
||||||
name: String,
|
name: String,
|
||||||
default: MetaItem?,
|
default: Meta?,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (MetaItem?) -> MetaItem,
|
getter: suspend (Meta?) -> Meta,
|
||||||
): ReadOnlyDeviceProperty {
|
): ReadOnlyDeviceProperty {
|
||||||
val property = BasicReadOnlyDeviceProperty(
|
val property = BasicReadOnlyDeviceProperty(
|
||||||
this,
|
this,
|
||||||
@ -197,10 +199,10 @@ public abstract class DeviceBase(final override val context: Context) : Device {
|
|||||||
*/
|
*/
|
||||||
internal fun createMutableProperty(
|
internal fun createMutableProperty(
|
||||||
name: String,
|
name: String,
|
||||||
default: MetaItem?,
|
default: Meta?,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (MetaItem?) -> MetaItem,
|
getter: suspend (Meta?) -> Meta,
|
||||||
setter: suspend (oldValue: MetaItem?, newValue: MetaItem) -> MetaItem?,
|
setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
||||||
): DeviceProperty {
|
): DeviceProperty {
|
||||||
val property = BasicDeviceProperty(
|
val property = BasicDeviceProperty(
|
||||||
this,
|
this,
|
||||||
@ -220,9 +222,9 @@ public abstract class DeviceBase(final override val context: Context) : Device {
|
|||||||
private inner class BasicDeviceAction(
|
private inner class BasicDeviceAction(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val descriptor: ActionDescriptor,
|
override val descriptor: ActionDescriptor,
|
||||||
private val block: suspend (MetaItem?) -> MetaItem?,
|
private val block: suspend (Meta?) -> Meta?,
|
||||||
) : DeviceAction {
|
) : DeviceAction {
|
||||||
override suspend fun invoke(arg: MetaItem?): MetaItem? =
|
override suspend fun invoke(arg: Meta?): Meta? =
|
||||||
withContext(coroutineContext) {
|
withContext(coroutineContext) {
|
||||||
block(arg)
|
block(arg)
|
||||||
}
|
}
|
||||||
@ -234,7 +236,7 @@ public abstract class DeviceBase(final override val context: Context) : Device {
|
|||||||
internal fun createAction(
|
internal fun createAction(
|
||||||
name: String,
|
name: String,
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
block: suspend (MetaItem?) -> MetaItem?,
|
block: suspend (Meta?) -> Meta?,
|
||||||
): DeviceAction {
|
): DeviceAction {
|
||||||
val action = BasicDeviceAction(name, ActionDescriptor(name).apply(descriptorBuilder), block)
|
val action = BasicDeviceAction(name, ActionDescriptor(name).apply(descriptorBuilder), block)
|
||||||
registerAction(name, action)
|
registerAction(name, action)
|
||||||
|
@ -3,7 +3,7 @@ package ru.mipt.npm.controls.base
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.Meta
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,24 +30,24 @@ public interface ReadOnlyDeviceProperty {
|
|||||||
/**
|
/**
|
||||||
* Directly update property logical value and notify listener without writing it to device
|
* Directly update property logical value and notify listener without writing it to device
|
||||||
*/
|
*/
|
||||||
public fun updateLogical(item: MetaItem)
|
public fun updateLogical(item: Meta)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cached value and return null if value is invalid or not initialized
|
* Get cached value and return null if value is invalid or not initialized
|
||||||
*/
|
*/
|
||||||
public val value: MetaItem?
|
public val value: Meta?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read value either from cache if cache is valid or directly from physical device.
|
* Read value either from cache if cache is valid or directly from physical device.
|
||||||
* If [force], reread from physical state even if the logical state is set.
|
* If [force], reread from physical state even if the logical state is set.
|
||||||
*/
|
*/
|
||||||
public suspend fun read(force: Boolean = false): MetaItem
|
public suspend fun read(force: Boolean = false): Meta
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [Flow] representing future logical states of the property.
|
* The [Flow] representing future logical states of the property.
|
||||||
* Produces null when the state is invalidated
|
* Produces null when the state is invalidated
|
||||||
*/
|
*/
|
||||||
public fun flow(): Flow<MetaItem?>
|
public fun flow(): Flow<Meta?>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -65,10 +65,10 @@ public fun ReadOnlyDeviceProperty.readEvery(duration: Duration): Job = scope.lau
|
|||||||
* A writeable device property with non-suspended write
|
* A writeable device property with non-suspended write
|
||||||
*/
|
*/
|
||||||
public interface DeviceProperty : ReadOnlyDeviceProperty {
|
public interface DeviceProperty : ReadOnlyDeviceProperty {
|
||||||
override var value: MetaItem?
|
override var value: Meta?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write value to physical device. Invalidates logical value, but does not update it automatically
|
* Write value to physical device. Invalidates logical value, but does not update it automatically
|
||||||
*/
|
*/
|
||||||
public suspend fun write(item: MetaItem)
|
public suspend fun write(item: Meta)
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ package ru.mipt.npm.controls.base
|
|||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,14 +14,18 @@ public open class TypedReadOnlyDeviceProperty<T : Any>(
|
|||||||
) : ReadOnlyDeviceProperty by property {
|
) : ReadOnlyDeviceProperty by property {
|
||||||
|
|
||||||
public fun updateLogical(obj: T) {
|
public fun updateLogical(obj: T) {
|
||||||
property.updateLogical(converter.objectToMetaItem(obj))
|
property.updateLogical(converter.objectToMeta(obj))
|
||||||
}
|
}
|
||||||
|
|
||||||
public open val typedValue: T? get() = value?.let { converter.itemToObject(it) }
|
public open val typedValue: T? get() = value?.let { converter.metaToObject(it) }
|
||||||
|
|
||||||
public suspend fun readTyped(force: Boolean = false): T = converter.itemToObject(read(force))
|
public suspend fun readTyped(force: Boolean = false): T {
|
||||||
|
val meta = read(force)
|
||||||
|
return converter.metaToObject(meta)
|
||||||
|
?: error("Meta $meta could not be converted by $converter")
|
||||||
|
}
|
||||||
|
|
||||||
public fun flowTyped(): Flow<T?> = flow().map { it?.let { converter.itemToObject(it) } }
|
public fun flowTyped(): Flow<T?> = flow().map { it?.let { converter.metaToObject(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,23 +36,23 @@ public class TypedDeviceProperty<T : Any>(
|
|||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
) : TypedReadOnlyDeviceProperty<T>(property, converter), DeviceProperty {
|
) : TypedReadOnlyDeviceProperty<T>(property, converter), DeviceProperty {
|
||||||
|
|
||||||
override var value: MetaItem?
|
override var value: Meta?
|
||||||
get() = property.value
|
get() = property.value
|
||||||
set(arg) {
|
set(arg) {
|
||||||
property.value = arg
|
property.value = arg
|
||||||
}
|
}
|
||||||
|
|
||||||
public override var typedValue: T?
|
public override var typedValue: T?
|
||||||
get() = value?.let { converter.itemToObject(it) }
|
get() = value?.let { converter.metaToObject(it) }
|
||||||
set(arg) {
|
set(arg) {
|
||||||
property.value = arg?.let { converter.objectToMetaItem(arg) }
|
property.value = arg?.let { converter.objectToMeta(arg) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun write(item: MetaItem) {
|
override suspend fun write(item: Meta) {
|
||||||
property.write(item)
|
property.write(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
public suspend fun write(obj: T) {
|
public suspend fun write(obj: T) {
|
||||||
property.write(converter.objectToMetaItem(obj))
|
property.write(converter.objectToMeta(obj))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,8 @@
|
|||||||
package ru.mipt.npm.controls.base
|
package ru.mipt.npm.controls.base
|
||||||
|
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
import ru.mipt.npm.controls.api.ActionDescriptor
|
||||||
import space.kscience.dataforge.meta.MetaBuilder
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.MutableMeta
|
||||||
import space.kscience.dataforge.meta.MetaItemNode
|
|
||||||
import space.kscience.dataforge.meta.MetaItemValue
|
|
||||||
import space.kscience.dataforge.values.Value
|
import space.kscience.dataforge.values.Value
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
import kotlin.properties.PropertyDelegateProvider
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
@ -22,7 +20,7 @@ public typealias ActionDelegate = ReadOnlyProperty<DeviceBase, DeviceAction>
|
|||||||
private class ActionProvider<D : DeviceBase>(
|
private class ActionProvider<D : DeviceBase>(
|
||||||
val owner: D,
|
val owner: D,
|
||||||
val descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
val descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
val block: suspend (MetaItem?) -> MetaItem?,
|
val block: suspend (Meta?) -> Meta?,
|
||||||
) : PropertyDelegateProvider<D, ActionDelegate> {
|
) : PropertyDelegateProvider<D, ActionDelegate> {
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ActionDelegate {
|
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ActionDelegate {
|
||||||
val name = property.name
|
val name = property.name
|
||||||
@ -33,28 +31,27 @@ private class ActionProvider<D : DeviceBase>(
|
|||||||
|
|
||||||
public fun DeviceBase.requesting(
|
public fun DeviceBase.requesting(
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
action: suspend (MetaItem?) -> MetaItem?,
|
action: suspend (Meta?) -> Meta?,
|
||||||
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder, action)
|
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder, action)
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.requestingValue(
|
public fun <D : DeviceBase> D.requestingValue(
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
action: suspend (MetaItem?) -> Any?,
|
action: suspend (Meta?) -> Any?,
|
||||||
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||||
val res = action(it)
|
val res = action(it)
|
||||||
MetaItemValue(Value.of(res))
|
Meta(Value.of(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.requestingMeta(
|
public fun <D : DeviceBase> D.requestingMeta(
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
action: suspend MetaBuilder.(MetaItem?) -> Unit,
|
action: suspend MutableMeta.(Meta?) -> Unit,
|
||||||
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||||
val res = MetaBuilder().apply { action(it) }
|
Meta { action(it) }
|
||||||
MetaItemNode(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun DeviceBase.acting(
|
public fun DeviceBase.acting(
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
action: suspend (MetaItem?) -> Unit,
|
action: suspend (Meta?) -> Unit,
|
||||||
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||||
action(it)
|
action(it)
|
||||||
null
|
null
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package ru.mipt.npm.controls.base
|
package ru.mipt.npm.controls.base
|
||||||
|
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.MutableMeta
|
||||||
|
import space.kscience.dataforge.meta.boolean
|
||||||
|
import space.kscience.dataforge.meta.double
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import space.kscience.dataforge.values.Null
|
import space.kscience.dataforge.values.Null
|
||||||
import space.kscience.dataforge.values.Value
|
import space.kscience.dataforge.values.Value
|
||||||
@ -29,9 +32,9 @@ public typealias TypedReadOnlyPropertyDelegate<T> = ReadOnlyProperty<DeviceBase,
|
|||||||
|
|
||||||
private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
|
private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
|
||||||
val owner: D,
|
val owner: D,
|
||||||
val default: MetaItem?,
|
val default: Meta?,
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
private val getter: suspend (MetaItem?) -> MetaItem,
|
private val getter: suspend (Meta?) -> Meta,
|
||||||
) : PropertyDelegateProvider<D, ReadOnlyPropertyDelegate> {
|
) : PropertyDelegateProvider<D, ReadOnlyPropertyDelegate> {
|
||||||
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate {
|
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate {
|
||||||
@ -43,10 +46,10 @@ private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
|
|||||||
|
|
||||||
private class TypedReadOnlyDevicePropertyProvider<D : DeviceBase, T : Any>(
|
private class TypedReadOnlyDevicePropertyProvider<D : DeviceBase, T : Any>(
|
||||||
val owner: D,
|
val owner: D,
|
||||||
val default: MetaItem?,
|
val default: Meta?,
|
||||||
val converter: MetaConverter<T>,
|
val converter: MetaConverter<T>,
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
private val getter: suspend (MetaItem?) -> MetaItem,
|
private val getter: suspend (Meta?) -> Meta,
|
||||||
) : PropertyDelegateProvider<D, TypedReadOnlyPropertyDelegate<T>> {
|
) : PropertyDelegateProvider<D, TypedReadOnlyPropertyDelegate<T>> {
|
||||||
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedReadOnlyPropertyDelegate<T> {
|
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedReadOnlyPropertyDelegate<T> {
|
||||||
@ -57,9 +60,9 @@ private class TypedReadOnlyDevicePropertyProvider<D : DeviceBase, T : Any>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun DeviceBase.reading(
|
public fun DeviceBase.reading(
|
||||||
default: MetaItem? = null,
|
default: Meta? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (MetaItem?) -> MetaItem,
|
getter: suspend (Meta?) -> Meta,
|
||||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
default,
|
default,
|
||||||
@ -73,9 +76,9 @@ public fun DeviceBase.readingValue(
|
|||||||
getter: suspend () -> Any?,
|
getter: suspend () -> Any?,
|
||||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItemValue(it) },
|
default?.let { Meta(it) },
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = { MetaItemValue(Value.of(getter())) }
|
getter = { Meta(Value.of(getter())) }
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun DeviceBase.readingNumber(
|
public fun DeviceBase.readingNumber(
|
||||||
@ -84,12 +87,12 @@ public fun DeviceBase.readingNumber(
|
|||||||
getter: suspend () -> Number,
|
getter: suspend () -> Number,
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Number>> = TypedReadOnlyDevicePropertyProvider(
|
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Number>> = TypedReadOnlyDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItemValue(it.asValue()) },
|
default?.let { Meta(it.asValue()) },
|
||||||
MetaConverter.number,
|
MetaConverter.number,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = {
|
getter = {
|
||||||
val number = getter()
|
val number = getter()
|
||||||
MetaItemValue(number.asValue())
|
Meta(number.asValue())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -99,12 +102,12 @@ public fun DeviceBase.readingDouble(
|
|||||||
getter: suspend () -> Double,
|
getter: suspend () -> Double,
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Double>> = TypedReadOnlyDevicePropertyProvider(
|
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Double>> = TypedReadOnlyDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItemValue(it.asValue()) },
|
default?.let { Meta(it.asValue()) },
|
||||||
MetaConverter.double,
|
MetaConverter.double,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = {
|
getter = {
|
||||||
val number = getter()
|
val number = getter()
|
||||||
MetaItemValue(number.asValue())
|
Meta(number.asValue())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -114,12 +117,12 @@ public fun DeviceBase.readingString(
|
|||||||
getter: suspend () -> String,
|
getter: suspend () -> String,
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<String>> = TypedReadOnlyDevicePropertyProvider(
|
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<String>> = TypedReadOnlyDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItemValue(it.asValue()) },
|
default?.let { Meta(it.asValue()) },
|
||||||
MetaConverter.string,
|
MetaConverter.string,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = {
|
getter = {
|
||||||
val number = getter()
|
val number = getter()
|
||||||
MetaItemValue(number.asValue())
|
Meta(number.asValue())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -129,26 +132,26 @@ public fun DeviceBase.readingBoolean(
|
|||||||
getter: suspend () -> Boolean,
|
getter: suspend () -> Boolean,
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Boolean>> = TypedReadOnlyDevicePropertyProvider(
|
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Boolean>> = TypedReadOnlyDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItemValue(it.asValue()) },
|
default?.let { Meta(it.asValue()) },
|
||||||
MetaConverter.boolean,
|
MetaConverter.boolean,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = {
|
getter = {
|
||||||
val boolean = getter()
|
val boolean = getter()
|
||||||
MetaItemValue(boolean.asValue())
|
Meta(boolean.asValue())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun DeviceBase.readingMeta(
|
public fun DeviceBase.readingMeta(
|
||||||
default: Meta? = null,
|
default: Meta? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend MetaBuilder.() -> Unit,
|
getter: suspend MutableMeta.() -> Unit,
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Meta>> = TypedReadOnlyDevicePropertyProvider(
|
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Meta>> = TypedReadOnlyDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItemNode(it) },
|
default,
|
||||||
MetaConverter.meta,
|
MetaConverter.meta,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = {
|
getter = {
|
||||||
MetaItemNode(MetaBuilder().apply { getter() })
|
Meta { getter() }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -170,10 +173,10 @@ public typealias TypedPropertyDelegate<T> = ReadOnlyProperty<DeviceBase, TypedDe
|
|||||||
|
|
||||||
private class DevicePropertyProvider<D : DeviceBase>(
|
private class DevicePropertyProvider<D : DeviceBase>(
|
||||||
val owner: D,
|
val owner: D,
|
||||||
val default: MetaItem?,
|
val default: Meta?,
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
private val getter: suspend (MetaItem?) -> MetaItem,
|
private val getter: suspend (Meta?) -> Meta,
|
||||||
private val setter: suspend (oldValue: MetaItem?, newValue: MetaItem) -> MetaItem?,
|
private val setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
||||||
) : PropertyDelegateProvider<D, PropertyDelegate> {
|
) : PropertyDelegateProvider<D, PropertyDelegate> {
|
||||||
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate {
|
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate {
|
||||||
@ -185,11 +188,11 @@ private class DevicePropertyProvider<D : DeviceBase>(
|
|||||||
|
|
||||||
private class TypedDevicePropertyProvider<D : DeviceBase, T : Any>(
|
private class TypedDevicePropertyProvider<D : DeviceBase, T : Any>(
|
||||||
val owner: D,
|
val owner: D,
|
||||||
val default: MetaItem?,
|
val default: Meta?,
|
||||||
val converter: MetaConverter<T>,
|
val converter: MetaConverter<T>,
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
private val getter: suspend (MetaItem?) -> MetaItem,
|
private val getter: suspend (Meta?) -> Meta,
|
||||||
private val setter: suspend (oldValue: MetaItem?, newValue: MetaItem) -> MetaItem?,
|
private val setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
||||||
) : PropertyDelegateProvider<D, TypedPropertyDelegate<T>> {
|
) : PropertyDelegateProvider<D, TypedPropertyDelegate<T>> {
|
||||||
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedPropertyDelegate<T> {
|
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedPropertyDelegate<T> {
|
||||||
@ -200,10 +203,10 @@ private class TypedDevicePropertyProvider<D : DeviceBase, T : Any>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun DeviceBase.writing(
|
public fun DeviceBase.writing(
|
||||||
default: MetaItem? = null,
|
default: Meta? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (MetaItem?) -> MetaItem,
|
getter: suspend (Meta?) -> Meta,
|
||||||
setter: suspend (oldValue: MetaItem?, newValue: MetaItem) -> MetaItem?,
|
setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
||||||
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = DevicePropertyProvider(
|
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = DevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
default,
|
default,
|
||||||
@ -213,7 +216,7 @@ public fun DeviceBase.writing(
|
|||||||
)
|
)
|
||||||
|
|
||||||
public fun DeviceBase.writingVirtual(
|
public fun DeviceBase.writingVirtual(
|
||||||
default: MetaItem,
|
default: Meta,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
||||||
default,
|
default,
|
||||||
@ -226,19 +229,9 @@ public fun DeviceBase.writingVirtual(
|
|||||||
default: Value,
|
default: Value,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
||||||
MetaItemValue(default),
|
Meta(default),
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = { it ?: MetaItemValue(default) },
|
getter = { it ?: Meta(default) },
|
||||||
setter = { _, newItem -> newItem }
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun DeviceBase.writingVirtual(
|
|
||||||
default: Meta,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
|
||||||
MetaItemNode(default),
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = { it ?: MetaItemNode(default) },
|
|
||||||
setter = { _, newItem -> newItem }
|
setter = { _, newItem -> newItem }
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -247,17 +240,17 @@ public fun <D : DeviceBase> D.writingDouble(
|
|||||||
getter: suspend (Double) -> Double,
|
getter: suspend (Double) -> Double,
|
||||||
setter: suspend (oldValue: Double?, newValue: Double) -> Double?,
|
setter: suspend (oldValue: Double?, newValue: Double) -> Double?,
|
||||||
): PropertyDelegateProvider<D, TypedPropertyDelegate<Double>> {
|
): PropertyDelegateProvider<D, TypedPropertyDelegate<Double>> {
|
||||||
val innerGetter: suspend (MetaItem?) -> MetaItem = {
|
val innerGetter: suspend (Meta?) -> Meta = {
|
||||||
MetaItemValue(getter(it.double ?: Double.NaN).asValue())
|
Meta(getter(it.double ?: Double.NaN).asValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
val innerSetter: suspend (oldValue: MetaItem?, newValue: MetaItem) -> MetaItem? = { oldValue, newValue ->
|
val innerSetter: suspend (oldValue: Meta?, newValue: Meta) -> Meta? = { oldValue, newValue ->
|
||||||
setter(oldValue.double, newValue.double ?: Double.NaN)?.asMetaItem()
|
setter(oldValue.double, newValue.double ?: Double.NaN)?.asMeta()
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypedDevicePropertyProvider(
|
return TypedDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
MetaItemValue(Double.NaN.asValue()),
|
Meta(Double.NaN.asValue()),
|
||||||
MetaConverter.double,
|
MetaConverter.double,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
innerGetter,
|
innerGetter,
|
||||||
@ -270,18 +263,18 @@ public fun <D : DeviceBase> D.writingBoolean(
|
|||||||
getter: suspend (Boolean?) -> Boolean,
|
getter: suspend (Boolean?) -> Boolean,
|
||||||
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?,
|
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?,
|
||||||
): PropertyDelegateProvider<D, TypedPropertyDelegate<Boolean>> {
|
): PropertyDelegateProvider<D, TypedPropertyDelegate<Boolean>> {
|
||||||
val innerGetter: suspend (MetaItem?) -> MetaItem = {
|
val innerGetter: suspend (Meta?) -> Meta = {
|
||||||
MetaItemValue(getter(it.boolean).asValue())
|
Meta(getter(it.boolean).asValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
val innerSetter: suspend (oldValue: MetaItem?, newValue: MetaItem) -> MetaItem? = { oldValue, newValue ->
|
val innerSetter: suspend (oldValue: Meta?, newValue: Meta) -> Meta? = { oldValue, newValue ->
|
||||||
setter(oldValue.boolean, newValue.boolean ?: error("Can't convert $newValue to boolean"))?.asValue()
|
setter(oldValue.boolean, newValue.boolean ?: error("Can't convert $newValue to boolean"))?.asValue()
|
||||||
?.asMetaItem()
|
?.let { Meta(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypedDevicePropertyProvider(
|
return TypedDevicePropertyProvider(
|
||||||
this,
|
this,
|
||||||
MetaItemValue(Null),
|
Meta(Null),
|
||||||
MetaConverter.boolean,
|
MetaConverter.boolean,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
innerGetter,
|
innerGetter,
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package ru.mipt.npm.controls.base
|
package ru.mipt.npm.controls.base
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.double
|
||||||
|
import space.kscience.dataforge.meta.enum
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import space.kscience.dataforge.values.asValue
|
import space.kscience.dataforge.values.asValue
|
||||||
import space.kscience.dataforge.values.double
|
import space.kscience.dataforge.values.double
|
||||||
@ -8,20 +11,18 @@ import kotlin.time.Duration
|
|||||||
import kotlin.time.DurationUnit
|
import kotlin.time.DurationUnit
|
||||||
import kotlin.time.toDuration
|
import kotlin.time.toDuration
|
||||||
|
|
||||||
public fun Double.asMetaItem(): MetaItemValue = MetaItemValue(asValue())
|
public fun Double.asMeta(): Meta = Meta(asValue())
|
||||||
|
|
||||||
//TODO to be moved to DF
|
//TODO to be moved to DF
|
||||||
public object DurationConverter : MetaConverter<Duration> {
|
public object DurationConverter : MetaConverter<Duration> {
|
||||||
override fun itemToObject(item: MetaItem): Duration = when (item) {
|
override fun metaToObject(meta: Meta): Duration = meta.value?.double?.toDuration(DurationUnit.SECONDS)
|
||||||
is MetaItemNode -> {
|
?: run {
|
||||||
val unit: DurationUnit = item.node["unit"].enum<DurationUnit>() ?: DurationUnit.SECONDS
|
val unit: DurationUnit = meta["unit"].enum<DurationUnit>() ?: DurationUnit.SECONDS
|
||||||
val value = item.node[Meta.VALUE_KEY].double ?: error("No value present for Duration")
|
val value = meta[Meta.VALUE_KEY].double ?: error("No value present for Duration")
|
||||||
value.toDuration(unit)
|
return@run value.toDuration(unit)
|
||||||
}
|
}
|
||||||
is MetaItemValue -> item.value.double.toDuration(DurationUnit.SECONDS)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun objectToMetaItem(obj: Duration): MetaItem = obj.toDouble(DurationUnit.SECONDS).asMetaItem()
|
override fun objectToMeta(obj: Duration): Meta = obj.toDouble(DurationUnit.SECONDS).asMeta()
|
||||||
}
|
}
|
||||||
|
|
||||||
public val MetaConverter.Companion.duration: MetaConverter<Duration> get() = DurationConverter
|
public val MetaConverter.Companion.duration: MetaConverter<Duration> get() = DurationConverter
|
@ -1,151 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.controllers
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import ru.mipt.npm.controls.api.*
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.toName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The [DeviceController] wraps device operations in [DeviceMessage]
|
|
||||||
*/
|
|
||||||
@OptIn(DFExperimental::class)
|
|
||||||
public class DeviceController(
|
|
||||||
public val device: Device,
|
|
||||||
public val deviceName: String,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val propertyChanges = device.propertyFlow.map { (propertyName: String, value: MetaItem) ->
|
|
||||||
PropertyChangedMessage(
|
|
||||||
sourceDevice = deviceName,
|
|
||||||
property = propertyName,
|
|
||||||
value = value,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The flow of outgoing messages
|
|
||||||
*/
|
|
||||||
public val messages: Flow<DeviceMessage> get() = propertyChanges
|
|
||||||
|
|
||||||
public suspend fun respondMessage(message: DeviceMessage): DeviceMessage =
|
|
||||||
respondMessage(device, deviceName, message)
|
|
||||||
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public const val GET_PROPERTY_ACTION: String = "read"
|
|
||||||
public const val SET_PROPERTY_ACTION: String = "write"
|
|
||||||
public const val EXECUTE_ACTION: String = "execute"
|
|
||||||
public const val PROPERTY_LIST_ACTION: String = "propertyList"
|
|
||||||
public const val ACTION_LIST_ACTION: String = "actionList"
|
|
||||||
|
|
||||||
// internal suspend fun respond(device: Device, deviceTarget: String, request: Envelope): Envelope {
|
|
||||||
// val target = request.meta["target"].string
|
|
||||||
// return try {
|
|
||||||
// if (device is Responder) {
|
|
||||||
// device.respond(request)
|
|
||||||
// } else if (request.data == null) {
|
|
||||||
// respondMessage(device, deviceTarget, DeviceMessage.fromMeta(request.meta)).toEnvelope()
|
|
||||||
// } else if (target != null && target != deviceTarget) {
|
|
||||||
// error("Wrong target name $deviceTarget expected but $target found")
|
|
||||||
// } else error("Device does not support binary response")
|
|
||||||
// } catch (ex: Exception) {
|
|
||||||
// val requestSourceName = request.meta[DeviceMessage.SOURCE_KEY].string
|
|
||||||
// DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = requestSourceName).toEnvelope()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
internal suspend fun respondMessage(
|
|
||||||
device: Device,
|
|
||||||
deviceTarget: String,
|
|
||||||
request: DeviceMessage,
|
|
||||||
): DeviceMessage = try {
|
|
||||||
when (request) {
|
|
||||||
is PropertyGetMessage -> {
|
|
||||||
PropertyChangedMessage(
|
|
||||||
property = request.property,
|
|
||||||
value = device.getOrReadItem(request.property),
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is PropertySetMessage -> {
|
|
||||||
if (request.value == null) {
|
|
||||||
device.invalidate(request.property)
|
|
||||||
} else {
|
|
||||||
device.writeItem(request.property, request.value)
|
|
||||||
}
|
|
||||||
PropertyChangedMessage(
|
|
||||||
property = request.property,
|
|
||||||
value = device.getOrReadItem(request.property),
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ActionExecuteMessage -> {
|
|
||||||
ActionResultMessage(
|
|
||||||
action = request.action,
|
|
||||||
result = device.execute(request.action, request.argument),
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is GetDescriptionMessage -> {
|
|
||||||
val descriptionMeta = Meta {
|
|
||||||
"properties" put {
|
|
||||||
device.propertyDescriptors.map { descriptor ->
|
|
||||||
descriptor.name put descriptor.toMeta()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"actions" put {
|
|
||||||
device.actionDescriptors.map { descriptor ->
|
|
||||||
descriptor.name put descriptor.toMeta()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DescriptionMessage(
|
|
||||||
description = descriptionMeta,
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is DescriptionMessage,
|
|
||||||
is PropertyChangedMessage,
|
|
||||||
is ActionResultMessage,
|
|
||||||
is BinaryNotificationMessage,
|
|
||||||
is DeviceErrorMessage,
|
|
||||||
is EmptyDeviceMessage,
|
|
||||||
is DeviceLogMessage,
|
|
||||||
-> {
|
|
||||||
//Those messages are ignored
|
|
||||||
EmptyDeviceMessage(
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice,
|
|
||||||
comment = "The message is ignored"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = request.sourceDevice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage {
|
|
||||||
return try {
|
|
||||||
val targetName = request.targetDevice?.toName() ?: Name.EMPTY
|
|
||||||
val device = this.getOrNull(targetName) ?: error("The device with name $targetName not found in $this")
|
|
||||||
DeviceController.respondMessage(device, targetName.toString(), request)
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
DeviceMessage.error(ex, sourceDevice = deviceName, targetDevice = request.sourceDevice)
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,15 +4,14 @@ import ru.mipt.npm.controls.api.Device
|
|||||||
import ru.mipt.npm.controls.api.DeviceHub
|
import ru.mipt.npm.controls.api.DeviceHub
|
||||||
import space.kscience.dataforge.context.*
|
import space.kscience.dataforge.context.*
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.MetaBuilder
|
import space.kscience.dataforge.meta.MutableMeta
|
||||||
import space.kscience.dataforge.meta.get
|
|
||||||
import space.kscience.dataforge.meta.string
|
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.NameToken
|
import space.kscience.dataforge.names.NameToken
|
||||||
|
import kotlin.collections.set
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
public class DeviceManager(override val deviceName: String = "") : AbstractPlugin(), DeviceHub {
|
public class DeviceManager : AbstractPlugin(), DeviceHub {
|
||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,10 +20,6 @@ public class DeviceManager(override val deviceName: String = "") : AbstractPlugi
|
|||||||
private val top = HashMap<NameToken, Device>()
|
private val top = HashMap<NameToken, Device>()
|
||||||
override val devices: Map<NameToken, Device> get() = top
|
override val devices: Map<NameToken, Device> get() = top
|
||||||
|
|
||||||
public val controller: HubController by lazy {
|
|
||||||
HubController(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun registerDevice(name: NameToken, device: Device) {
|
public fun registerDevice(name: NameToken, device: Device) {
|
||||||
top[name] = device
|
top[name] = device
|
||||||
}
|
}
|
||||||
@ -35,8 +30,7 @@ public class DeviceManager(override val deviceName: String = "") : AbstractPlugi
|
|||||||
override val tag: PluginTag = PluginTag("devices", group = PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag("devices", group = PluginTag.DATAFORGE_GROUP)
|
||||||
override val type: KClass<out DeviceManager> = DeviceManager::class
|
override val type: KClass<out DeviceManager> = DeviceManager::class
|
||||||
|
|
||||||
override fun invoke(meta: Meta, context: Context): DeviceManager =
|
override fun invoke(meta: Meta, context: Context): DeviceManager = DeviceManager()
|
||||||
DeviceManager(meta["deviceName"].string ?: "")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,11 +41,14 @@ public fun <D : Device> DeviceManager.install(name: String, factory: Factory<D>,
|
|||||||
return device
|
return device
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <D : Device> DeviceManager.installing(
|
public inline fun <D : Device> DeviceManager.installing(
|
||||||
factory: Factory<D>,
|
factory: Factory<D>,
|
||||||
metaBuilder: MetaBuilder.() -> Unit = {},
|
builder: MutableMeta.() -> Unit = {},
|
||||||
): ReadOnlyProperty<Any?, D> = ReadOnlyProperty { _, property ->
|
): ReadOnlyProperty<Any?, D> {
|
||||||
val name = property.name
|
val meta = Meta(builder)
|
||||||
install(name, factory, Meta(metaBuilder))
|
return ReadOnlyProperty { _, property ->
|
||||||
|
val name = property.name
|
||||||
|
install(name, factory, meta)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.controllers
|
|
||||||
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.consumeAsFlow
|
|
||||||
import ru.mipt.npm.controls.api.DeviceHub
|
|
||||||
import ru.mipt.npm.controls.api.DeviceMessage
|
|
||||||
import ru.mipt.npm.controls.api.getOrNull
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.toName
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
|
||||||
public class HubController(
|
|
||||||
public val hub: DeviceHub,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val messageOutbox = Channel<DeviceMessage>(Channel.CONFLATED)
|
|
||||||
|
|
||||||
// private val envelopeOutbox = Channel<Envelope>(Channel.CONFLATED)
|
|
||||||
|
|
||||||
public fun messageOutput(): Flow<DeviceMessage> = messageOutbox.consumeAsFlow()
|
|
||||||
|
|
||||||
// public fun envelopeOutput(): Flow<Envelope> = envelopeOutbox.consumeAsFlow()
|
|
||||||
|
|
||||||
// private val packJob = scope.launch {
|
|
||||||
// while (isActive) {
|
|
||||||
// val message = messageOutbox.receive()
|
|
||||||
// envelopeOutbox.send(message.toEnvelope())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private val listeners: Map<NameToken, DeviceListener> = hub.devices.mapValues { (deviceNameToken, device) ->
|
|
||||||
// object : DeviceListener {
|
|
||||||
// override fun propertyChanged(propertyName: String, value: MetaItem?) {
|
|
||||||
// if (value == null) return
|
|
||||||
// scope.launch {
|
|
||||||
// val change = PropertyChangedMessage(
|
|
||||||
// sourceDevice = deviceNameToken.toString(),
|
|
||||||
// key = propertyName,
|
|
||||||
// value = value
|
|
||||||
// )
|
|
||||||
// messageOutbox.send(change)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }.also {
|
|
||||||
// device.registerListener(it)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
public suspend fun respondMessage(message: DeviceMessage): DeviceMessage = try {
|
|
||||||
val targetName = message.targetDevice?.toName() ?: Name.EMPTY
|
|
||||||
val device = hub.getOrNull(targetName) ?: error("The device with name $targetName not found in $hub")
|
|
||||||
DeviceController.respondMessage(device, targetName.toString(), message)
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
DeviceMessage.error(ex, sourceDevice = hub.deviceName, targetDevice = message.sourceDevice)
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// override suspend fun respond(request: Envelope): Envelope = try {
|
|
||||||
// val targetName = request.meta[DeviceMessage.TARGET_KEY].string?.toName() ?: Name.EMPTY
|
|
||||||
// val device = hub[targetName] ?: error("The device with name $targetName not found in $hub")
|
|
||||||
// if (request.data == null) {
|
|
||||||
// DeviceController.respondMessage(device, targetName.toString(), DeviceMessage.fromMeta(request.meta))
|
|
||||||
// .toEnvelope()
|
|
||||||
// } else {
|
|
||||||
// DeviceController.respond(device, targetName.toString(), request)
|
|
||||||
// }
|
|
||||||
// } catch (ex: Exception) {
|
|
||||||
// DeviceMessage.error(ex, sourceDevice = null).toEnvelope()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// override fun consume(message: Envelope) {
|
|
||||||
// // Fire the respond procedure and forget about the result
|
|
||||||
// scope.launch {
|
|
||||||
// respond(message)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
@ -0,0 +1,115 @@
|
|||||||
|
package ru.mipt.npm.controls.controllers
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import ru.mipt.npm.controls.api.*
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.plus
|
||||||
|
|
||||||
|
public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMessage): DeviceMessage? = try {
|
||||||
|
when (request) {
|
||||||
|
is PropertyGetMessage -> {
|
||||||
|
PropertyChangedMessage(
|
||||||
|
property = request.property,
|
||||||
|
value = getOrReadItem(request.property),
|
||||||
|
sourceDevice = deviceTarget,
|
||||||
|
targetDevice = request.sourceDevice
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is PropertySetMessage -> {
|
||||||
|
if (request.value == null) {
|
||||||
|
invalidate(request.property)
|
||||||
|
} else {
|
||||||
|
writeItem(request.property, request.value)
|
||||||
|
}
|
||||||
|
PropertyChangedMessage(
|
||||||
|
property = request.property,
|
||||||
|
value = getOrReadItem(request.property),
|
||||||
|
sourceDevice = deviceTarget,
|
||||||
|
targetDevice = request.sourceDevice
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ActionExecuteMessage -> {
|
||||||
|
ActionResultMessage(
|
||||||
|
action = request.action,
|
||||||
|
result = execute(request.action, request.argument),
|
||||||
|
sourceDevice = deviceTarget,
|
||||||
|
targetDevice = request.sourceDevice
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is GetDescriptionMessage -> {
|
||||||
|
val descriptionMeta = Meta {
|
||||||
|
"properties" put {
|
||||||
|
propertyDescriptors.map { descriptor ->
|
||||||
|
descriptor.name put descriptor.toMeta()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"actions" put {
|
||||||
|
actionDescriptors.map { descriptor ->
|
||||||
|
descriptor.name put descriptor.toMeta()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptionMessage(
|
||||||
|
description = descriptionMeta,
|
||||||
|
sourceDevice = deviceTarget,
|
||||||
|
targetDevice = request.sourceDevice
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is DescriptionMessage,
|
||||||
|
is PropertyChangedMessage,
|
||||||
|
is ActionResultMessage,
|
||||||
|
is BinaryNotificationMessage,
|
||||||
|
is DeviceErrorMessage,
|
||||||
|
is EmptyDeviceMessage,
|
||||||
|
is DeviceLogMessage,
|
||||||
|
-> null
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = request.sourceDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
public suspend fun DeviceHub.respondHubMessage(request: DeviceMessage): 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)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
DeviceMessage.error(ex, sourceDevice = Name.EMPTY, targetDevice = request.sourceDevice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all messages from given [DeviceHub], applying proper relative names
|
||||||
|
*/
|
||||||
|
public fun DeviceHub.hubMessageFlow(scope: CoroutineScope): Flow<DeviceMessage> {
|
||||||
|
val outbox = MutableSharedFlow<DeviceMessage>()
|
||||||
|
if (this is Device) {
|
||||||
|
messageFlow.onEach {
|
||||||
|
outbox.emit(it)
|
||||||
|
}.launchIn(scope)
|
||||||
|
}
|
||||||
|
//TODO maybe better create map of all devices to limit copying
|
||||||
|
devices.forEach { (token, childDevice) ->
|
||||||
|
val flow = if (childDevice is DeviceHub) {
|
||||||
|
childDevice.hubMessageFlow(scope)
|
||||||
|
} else {
|
||||||
|
childDevice.messageFlow
|
||||||
|
}
|
||||||
|
flow.onEach { deviceMessage ->
|
||||||
|
outbox.emit(
|
||||||
|
deviceMessage.changeSource { token + it }
|
||||||
|
)
|
||||||
|
}.launchIn(scope)
|
||||||
|
}
|
||||||
|
return outbox
|
||||||
|
}
|
@ -7,13 +7,10 @@ import kotlinx.coroutines.flow.SharedFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
import ru.mipt.npm.controls.api.*
|
||||||
import ru.mipt.npm.controls.api.Device
|
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.properties.Delegates.observable
|
import kotlin.properties.Delegates.observable
|
||||||
@ -48,11 +45,11 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
||||||
}
|
}
|
||||||
|
|
||||||
private val logicalState: HashMap<String, MetaItem?> = HashMap()
|
private val logicalState: HashMap<String, Meta?> = HashMap()
|
||||||
|
|
||||||
private val _propertyFlow: MutableSharedFlow<Pair<String, MetaItem>> = MutableSharedFlow()
|
private val sharedMessageFlow: MutableSharedFlow<DeviceMessage> = MutableSharedFlow()
|
||||||
|
|
||||||
override val propertyFlow: SharedFlow<Pair<String, MetaItem>> get() = _propertyFlow
|
public override val messageFlow: SharedFlow<DeviceMessage> get() = sharedMessageFlow
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
internal val self: D
|
internal val self: D
|
||||||
@ -60,13 +57,13 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
|
|
||||||
private val stateLock = Mutex()
|
private val stateLock = Mutex()
|
||||||
|
|
||||||
private suspend fun updateLogical(propertyName: String, value: MetaItem?) {
|
private suspend fun updateLogical(propertyName: String, value: Meta?) {
|
||||||
if (value != logicalState[propertyName]) {
|
if (value != logicalState[propertyName]) {
|
||||||
stateLock.withLock {
|
stateLock.withLock {
|
||||||
logicalState[propertyName] = value
|
logicalState[propertyName] = value
|
||||||
}
|
}
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
_propertyFlow.emit(propertyName to value)
|
sharedMessageFlow.emit(PropertyChangedMessage(propertyName, value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,14 +72,14 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
* Force read physical value and push an update if it is changed. It does not matter if logical state is present.
|
* Force read physical value and push an update if it is changed. It does not matter if logical state is present.
|
||||||
* The logical state is updated after read
|
* The logical state is updated after read
|
||||||
*/
|
*/
|
||||||
override suspend fun readItem(propertyName: String): MetaItem {
|
override suspend fun readProperty(propertyName: String): Meta {
|
||||||
val newValue = properties[propertyName]?.readItem(self)
|
val newValue = properties[propertyName]?.readItem(self)
|
||||||
?: error("A property with name $propertyName is not registered in $this")
|
?: error("A property with name $propertyName is not registered in $this")
|
||||||
updateLogical(propertyName, newValue)
|
updateLogical(propertyName, newValue)
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItem(propertyName: String): MetaItem? = logicalState[propertyName]
|
override fun getProperty(propertyName: String): Meta? = logicalState[propertyName]
|
||||||
|
|
||||||
override suspend fun invalidate(propertyName: String) {
|
override suspend fun invalidate(propertyName: String) {
|
||||||
stateLock.withLock {
|
stateLock.withLock {
|
||||||
@ -90,7 +87,7 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeItem(propertyName: String, value: MetaItem): Unit {
|
override suspend fun writeItem(propertyName: String, value: Meta): Unit {
|
||||||
//If there is a physical property with given name, invalidate logical property and write physical one
|
//If there is a physical property with given name, invalidate logical property and write physical one
|
||||||
(properties[propertyName] as? WritableDevicePropertySpec<D, out Any>)?.let {
|
(properties[propertyName] as? WritableDevicePropertySpec<D, out Any>)?.let {
|
||||||
it.writeItem(self, value)
|
it.writeItem(self, value)
|
||||||
@ -100,7 +97,7 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(action: String, argument: MetaItem?): MetaItem? =
|
override suspend fun execute(action: String, argument: Meta?): Meta? =
|
||||||
actions[action]?.executeItem(self, argument)
|
actions[action]?.executeItem(self, argument)
|
||||||
|
|
||||||
|
|
||||||
@ -114,7 +111,7 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
if (oldValue != newValue) {
|
if (oldValue != newValue) {
|
||||||
launch {
|
launch {
|
||||||
invalidate(property.name)
|
invalidate(property.name)
|
||||||
_propertyFlow.emit(property.name to converter.objectToMetaItem(newValue))
|
sharedMessageFlow.emit(PropertyChangedMessage(property.name, converter.objectToMeta(newValue)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,11 +121,11 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
*/
|
*/
|
||||||
public suspend fun <T : Any> DevicePropertySpec<D, T>.read(): T {
|
public suspend fun <T : Any> DevicePropertySpec<D, T>.read(): T {
|
||||||
val res = read(self)
|
val res = read(self)
|
||||||
updateLogical(name, converter.objectToMetaItem(res))
|
updateLogical(name, converter.objectToMeta(res))
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> DevicePropertySpec<D, T>.get(): T? = getItem(name)?.let(converter::itemToObject)
|
public fun <T : Any> DevicePropertySpec<D, T>.get(): T? = getProperty(name)?.let(converter::metaToObject)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write typed property state and invalidate logical state
|
* Write typed property state and invalidate logical state
|
||||||
|
@ -3,10 +3,10 @@ package ru.mipt.npm.controls.properties
|
|||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
import ru.mipt.npm.controls.api.ActionDescriptor
|
||||||
import ru.mipt.npm.controls.api.Device
|
import ru.mipt.npm.controls.api.Device
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import space.kscience.dataforge.meta.transformations.nullableItemToObject
|
import space.kscience.dataforge.meta.transformations.nullableMetaToObject
|
||||||
import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem
|
import space.kscience.dataforge.meta.transformations.nullableObjectToMeta
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,8 +40,8 @@ public interface DevicePropertySpec<in D : Device, T : Any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public suspend fun <D : Device, T : Any> DevicePropertySpec<D, T>.readItem(device: D): MetaItem =
|
public suspend fun <D : Device, T : Any> DevicePropertySpec<D, T>.readItem(device: D): Meta =
|
||||||
converter.objectToMetaItem(read(device))
|
converter.objectToMeta(read(device))
|
||||||
|
|
||||||
|
|
||||||
public interface WritableDevicePropertySpec<in D : Device, T : Any> : DevicePropertySpec<D, T> {
|
public interface WritableDevicePropertySpec<in D : Device, T : Any> : DevicePropertySpec<D, T> {
|
||||||
@ -53,8 +53,8 @@ public interface WritableDevicePropertySpec<in D : Device, T : Any> : DeviceProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public suspend fun <D : Device, T : Any> WritableDevicePropertySpec<D, T>.writeItem(device: D, item: MetaItem) {
|
public suspend fun <D : Device, T : Any> WritableDevicePropertySpec<D, T>.writeItem(device: D, item: Meta) {
|
||||||
write(device, converter.itemToObject(item))
|
write(device, converter.metaToObject(item) ?: error("Meta $item could not be read with $converter"))
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DeviceActionSpec<in D : Device, I : Any, O : Any> {
|
public interface DeviceActionSpec<in D : Device, I : Any, O : Any> {
|
||||||
@ -80,9 +80,9 @@ public interface DeviceActionSpec<in D : Device, I : Any, O : Any> {
|
|||||||
|
|
||||||
public suspend fun <D : Device, I : Any, O : Any> DeviceActionSpec<D, I, O>.executeItem(
|
public suspend fun <D : Device, I : Any, O : Any> DeviceActionSpec<D, I, O>.executeItem(
|
||||||
device: D,
|
device: D,
|
||||||
item: MetaItem?
|
item: Meta?
|
||||||
): MetaItem? {
|
): Meta? {
|
||||||
val arg = inputConverter.nullableItemToObject(item)
|
val arg = inputConverter.nullableMetaToObject(item)
|
||||||
val res = execute(device, arg)
|
val res = execute(device, arg)
|
||||||
return outputConverter.nullableObjectToMetaItem(res)
|
return outputConverter.nullableObjectToMeta(res)
|
||||||
}
|
}
|
@ -2,8 +2,6 @@ package ru.mipt.npm.controls.properties
|
|||||||
|
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
|
||||||
import space.kscience.dataforge.meta.TypedMetaItem
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
import kotlin.properties.PropertyDelegateProvider
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
@ -39,13 +37,6 @@ public fun <D : DeviceBySpec<D>> DeviceSpec<D>.stringProperty(
|
|||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> =
|
||||||
property(MetaConverter.string, name, descriptorBuilder, read)
|
property(MetaConverter.string, name, descriptorBuilder, read)
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.itemProperty(
|
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
read: suspend D.() -> MetaItem
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, MetaItem>>> =
|
|
||||||
property(MetaConverter.item, name, descriptorBuilder, read)
|
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.metaProperty(
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.metaProperty(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
@ -88,14 +79,6 @@ public fun <D : DeviceBySpec<D>> DeviceSpec<D>.stringProperty(
|
|||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
||||||
property(MetaConverter.string, name, descriptorBuilder, read, write)
|
property(MetaConverter.string, name, descriptorBuilder, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.itemProperty(
|
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
read: suspend D.() -> MetaItem,
|
|
||||||
write: suspend D.(MetaItem) -> Unit
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, TypedMetaItem<*>>>> =
|
|
||||||
property(MetaConverter.item, name, descriptorBuilder, read, write)
|
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.metaProperty(
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.metaProperty(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
@ -2,7 +2,7 @@ package ru.mipt.npm.controls.controllers
|
|||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import ru.mipt.npm.controls.base.*
|
import ru.mipt.npm.controls.base.*
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
@ -12,7 +12,7 @@ import kotlin.time.Duration
|
|||||||
/**
|
/**
|
||||||
* Blocking read of the value
|
* Blocking read of the value
|
||||||
*/
|
*/
|
||||||
public operator fun ReadOnlyDeviceProperty.getValue(thisRef: Any?, property: KProperty<*>): MetaItem =
|
public operator fun ReadOnlyDeviceProperty.getValue(thisRef: Any?, property: KProperty<*>): Meta =
|
||||||
runBlocking(scope.coroutineContext) {
|
runBlocking(scope.coroutineContext) {
|
||||||
read()
|
read()
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ public operator fun <T: Any> TypedReadOnlyDeviceProperty<T>.getValue(thisRef: An
|
|||||||
readTyped()
|
readTyped()
|
||||||
}
|
}
|
||||||
|
|
||||||
public operator fun DeviceProperty.setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem) {
|
public operator fun DeviceProperty.setValue(thisRef: Any?, property: KProperty<*>, value: Meta) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +36,8 @@ public fun <T : Any> ReadOnlyDeviceProperty.convert(
|
|||||||
): ReadOnlyProperty<Any?, T> {
|
): ReadOnlyProperty<Any?, T> {
|
||||||
return ReadOnlyProperty { _, _ ->
|
return ReadOnlyProperty { _, _ ->
|
||||||
runBlocking(scope.coroutineContext) {
|
runBlocking(scope.coroutineContext) {
|
||||||
read(forceRead).let { metaConverter.itemToObject(it) }
|
val meta = read(forceRead)
|
||||||
|
metaConverter.metaToObject(meta)?: error("Meta $meta could not be converted by $metaConverter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,11 +48,12 @@ public fun <T : Any> DeviceProperty.convert(
|
|||||||
): ReadWriteProperty<Any?, T> {
|
): ReadWriteProperty<Any?, T> {
|
||||||
return object : ReadWriteProperty<Any?, T> {
|
return object : ReadWriteProperty<Any?, T> {
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T = runBlocking(scope.coroutineContext) {
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T = runBlocking(scope.coroutineContext) {
|
||||||
read(forceRead).let { metaConverter.itemToObject(it) }
|
val meta = read(forceRead)
|
||||||
|
metaConverter.metaToObject(meta)?: error("Meta $meta could not be converted by $metaConverter")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
this@convert.setValue(thisRef, property, value.let { metaConverter.objectToMetaItem(it) })
|
this@convert.setValue(thisRef, property, value.let { metaConverter.objectToMeta(it) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.controls.api.DeviceMessage
|
import ru.mipt.npm.controls.api.DeviceMessage
|
||||||
import ru.mipt.npm.controls.controllers.DeviceManager
|
import ru.mipt.npm.controls.controllers.DeviceManager
|
||||||
import ru.mipt.npm.controls.controllers.respondMessage
|
import ru.mipt.npm.controls.controllers.hubMessageFlow
|
||||||
|
import ru.mipt.npm.controls.controllers.respondHubMessage
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||||
import ru.mipt.npm.magix.api.MagixMessage
|
import ru.mipt.npm.magix.api.MagixMessage
|
||||||
import space.kscience.dataforge.context.error
|
import space.kscience.dataforge.context.error
|
||||||
@ -25,25 +26,28 @@ internal fun generateId(request: MagixMessage<*>): String = if (request.id != nu
|
|||||||
/**
|
/**
|
||||||
* Communicate with server in [Magix format](https://github.com/waltz-controls/rfc/tree/master/1)
|
* Communicate with server in [Magix format](https://github.com/waltz-controls/rfc/tree/master/1)
|
||||||
*/
|
*/
|
||||||
public fun DeviceManager.launchDfMagix(
|
public fun DeviceManager.connectToMagix(
|
||||||
endpoint: MagixEndpoint<DeviceMessage>,
|
endpoint: MagixEndpoint<DeviceMessage>,
|
||||||
endpointID: String = DATAFORGE_MAGIX_FORMAT,
|
endpointID: String = DATAFORGE_MAGIX_FORMAT,
|
||||||
): Job = context.launch {
|
): Job = context.launch {
|
||||||
endpoint.subscribe().onEach { request ->
|
endpoint.subscribe().onEach { request ->
|
||||||
val responsePayload = respondMessage(request.payload)
|
val responsePayload = respondHubMessage(request.payload)
|
||||||
val response = MagixMessage(
|
if (responsePayload != null) {
|
||||||
format = DATAFORGE_MAGIX_FORMAT,
|
val response = MagixMessage(
|
||||||
id = generateId(request),
|
format = DATAFORGE_MAGIX_FORMAT,
|
||||||
parentId = request.id,
|
id = generateId(request),
|
||||||
origin = endpointID,
|
parentId = request.id,
|
||||||
payload = responsePayload
|
origin = endpointID,
|
||||||
)
|
payload = responsePayload
|
||||||
endpoint.broadcast(response)
|
)
|
||||||
|
|
||||||
|
endpoint.broadcast(response)
|
||||||
|
}
|
||||||
}.catch { error ->
|
}.catch { error ->
|
||||||
logger.error(error) { "Error while responding to message" }
|
logger.error(error) { "Error while responding to message" }
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
controller.messageOutput().onEach { payload ->
|
hubMessageFlow(this).onEach { payload ->
|
||||||
endpoint.broadcast(
|
endpoint.broadcast(
|
||||||
MagixMessage(
|
MagixMessage(
|
||||||
format = DATAFORGE_MAGIX_FORMAT,
|
format = DATAFORGE_MAGIX_FORMAT,
|
||||||
|
@ -2,7 +2,7 @@ package ru.mipt.npm.controls.client
|
|||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -31,7 +31,7 @@ public data class EqData(
|
|||||||
@SerialName("type_id")
|
@SerialName("type_id")
|
||||||
val typeId: Int,
|
val typeId: Int,
|
||||||
val type: String? = null,
|
val type: String? = null,
|
||||||
val value: MetaItem? = null,
|
val value: Meta? = null,
|
||||||
@SerialName("event_id")
|
@SerialName("event_id")
|
||||||
val eventId: Int? = null,
|
val eventId: Int? = null,
|
||||||
val error: Int? = null,
|
val error: Int? = null,
|
||||||
|
@ -12,7 +12,7 @@ import ru.mipt.npm.magix.api.MagixEndpoint
|
|||||||
import ru.mipt.npm.magix.api.MagixMessage
|
import ru.mipt.npm.magix.api.MagixMessage
|
||||||
import space.kscience.dataforge.context.error
|
import space.kscience.dataforge.context.error
|
||||||
import space.kscience.dataforge.context.logger
|
import space.kscience.dataforge.context.logger
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
|
||||||
public const val TANGO_MAGIX_FORMAT: String = "tango"
|
public const val TANGO_MAGIX_FORMAT: String = "tango"
|
||||||
|
|
||||||
@ -54,11 +54,11 @@ public data class TangoPayload(
|
|||||||
val host: String,
|
val host: String,
|
||||||
val device: String,
|
val device: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val value: MetaItem? = null,
|
val value: Meta? = null,
|
||||||
val quality: TangoQuality = TangoQuality.VALID,
|
val quality: TangoQuality = TangoQuality.VALID,
|
||||||
val argin: MetaItem? = null,
|
val argin: Meta? = null,
|
||||||
val argout: MetaItem? = null,
|
val argout: Meta? = null,
|
||||||
val data: MetaItem? = null,
|
val data: Meta? = null,
|
||||||
val errors: List<String>? = null
|
val errors: List<String>? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,13 +29,15 @@ import ru.mipt.npm.controls.api.PropertyGetMessage
|
|||||||
import ru.mipt.npm.controls.api.PropertySetMessage
|
import ru.mipt.npm.controls.api.PropertySetMessage
|
||||||
import ru.mipt.npm.controls.api.getOrNull
|
import ru.mipt.npm.controls.api.getOrNull
|
||||||
import ru.mipt.npm.controls.controllers.DeviceManager
|
import ru.mipt.npm.controls.controllers.DeviceManager
|
||||||
import ru.mipt.npm.controls.controllers.respondMessage
|
import ru.mipt.npm.controls.controllers.respondHubMessage
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||||
import ru.mipt.npm.magix.server.GenericMagixMessage
|
import ru.mipt.npm.magix.server.GenericMagixMessage
|
||||||
import ru.mipt.npm.magix.server.launchMagixServerRawRSocket
|
import ru.mipt.npm.magix.server.launchMagixServerRawRSocket
|
||||||
import ru.mipt.npm.magix.server.magixModule
|
import ru.mipt.npm.magix.server.magixModule
|
||||||
import space.kscience.dataforge.meta.toJson
|
import space.kscience.dataforge.meta.toJson
|
||||||
import space.kscience.dataforge.meta.toMetaItem
|
import space.kscience.dataforge.meta.toMeta
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.asName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and start a web server for several devices
|
* Create and start a web server for several devices
|
||||||
@ -70,7 +72,7 @@ public fun ApplicationEngine.whenStarted(callback: Application.() -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public const val WEB_SERVER_TARGET: String = "@webServer"
|
public val WEB_SERVER_TARGET: Name = "@webServer".asName()
|
||||||
|
|
||||||
public fun Application.deviceManagerModule(
|
public fun Application.deviceManagerModule(
|
||||||
manager: DeviceManager,
|
manager: DeviceManager,
|
||||||
@ -158,7 +160,7 @@ public fun Application.deviceManagerModule(
|
|||||||
|
|
||||||
val request: DeviceMessage = MagixEndpoint.magixJson.decodeFromString(DeviceMessage.serializer(), body)
|
val request: DeviceMessage = MagixEndpoint.magixJson.decodeFromString(DeviceMessage.serializer(), body)
|
||||||
|
|
||||||
val response = manager.respondMessage(request)
|
val response = manager.respondHubMessage(request)
|
||||||
call.respondMessage(response)
|
call.respondMessage(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,11 +173,11 @@ public fun Application.deviceManagerModule(
|
|||||||
val property: String by call.parameters
|
val property: String by call.parameters
|
||||||
val request = PropertyGetMessage(
|
val request = PropertyGetMessage(
|
||||||
sourceDevice = WEB_SERVER_TARGET,
|
sourceDevice = WEB_SERVER_TARGET,
|
||||||
targetDevice = target,
|
targetDevice = Name.parse(target),
|
||||||
property = property,
|
property = property,
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = manager.respondMessage(request)
|
val response = manager.respondHubMessage(request)
|
||||||
call.respondMessage(response)
|
call.respondMessage(response)
|
||||||
}
|
}
|
||||||
post("set") {
|
post("set") {
|
||||||
@ -186,12 +188,12 @@ public fun Application.deviceManagerModule(
|
|||||||
|
|
||||||
val request = PropertySetMessage(
|
val request = PropertySetMessage(
|
||||||
sourceDevice = WEB_SERVER_TARGET,
|
sourceDevice = WEB_SERVER_TARGET,
|
||||||
targetDevice = target,
|
targetDevice = Name.parse(target),
|
||||||
property = property,
|
property = property,
|
||||||
value = json.toMetaItem()
|
value = json.toMeta()
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = manager.respondMessage(request)
|
val response = manager.respondHubMessage(request)
|
||||||
call.respondMessage(response)
|
call.respondMessage(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,15 +12,21 @@ repositories{
|
|||||||
maven("https://kotlin.bintray.com/kotlinx")
|
maven("https://kotlin.bintray.com/kotlinx")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ktorVersion: String by rootProject.extra
|
||||||
|
val rsocketVersion: String by rootProject.extra
|
||||||
|
|
||||||
dependencies{
|
dependencies{
|
||||||
implementation(projects.controlsCore)
|
implementation(projects.controlsCore)
|
||||||
//implementation(projects.controlsServer)
|
//implementation(projects.controlsServer)
|
||||||
implementation(projects.magix.magixServer)
|
implementation(projects.magix.magixServer)
|
||||||
implementation(projects.controlsMagixClient)
|
implementation(projects.controlsMagixClient)
|
||||||
implementation(projects.magix.magixRsocket)
|
implementation(projects.magix.magixRsocket)
|
||||||
|
implementation(projects.magix.magixZmq)
|
||||||
|
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.4.2")
|
implementation("space.kscience:plotlykt-server:0.5.0-dev-1")
|
||||||
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
||||||
|
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
|
@ -7,11 +7,12 @@ import javafx.scene.layout.Priority
|
|||||||
import javafx.stage.Stage
|
import javafx.stage.Stage
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.controls.api.DeviceMessage
|
import ru.mipt.npm.controls.api.DeviceMessage
|
||||||
import ru.mipt.npm.controls.client.launchDfMagix
|
import ru.mipt.npm.controls.client.connectToMagix
|
||||||
import ru.mipt.npm.controls.controllers.DeviceManager
|
import ru.mipt.npm.controls.controllers.DeviceManager
|
||||||
import ru.mipt.npm.controls.controllers.install
|
import ru.mipt.npm.controls.controllers.install
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||||
import ru.mipt.npm.magix.rsocket.rSocketWithTcp
|
import ru.mipt.npm.magix.rsocket.rSocketWithTcp
|
||||||
|
import ru.mipt.npm.magix.rsocket.rSocketWithWebSockets
|
||||||
import ru.mipt.npm.magix.server.startMagixServer
|
import ru.mipt.npm.magix.server.startMagixServer
|
||||||
import space.kscience.dataforge.context.*
|
import space.kscience.dataforge.context.*
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
@ -34,16 +35,18 @@ class DemoController : Controller(), ContextAware {
|
|||||||
context.launch {
|
context.launch {
|
||||||
device = deviceManager.install("demo", DemoDevice)
|
device = deviceManager.install("demo", DemoDevice)
|
||||||
//starting magix event loop
|
//starting magix event loop
|
||||||
magixServer = startMagixServer()
|
magixServer = startMagixServer(enableRawRSocket = true, enableZmq = true)
|
||||||
//Launch device client and connect it to the server
|
//Launch device client and connect it to the server
|
||||||
deviceManager.launchDfMagix(MagixEndpoint.rSocketWithTcp("localhost", DeviceMessage.serializer()))
|
val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost", DeviceMessage.serializer())
|
||||||
visualizer = startDemoDeviceServer()
|
deviceManager.connectToMagix(deviceEndpoint)
|
||||||
|
val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost", DeviceMessage.serializer())
|
||||||
|
visualizer = visualEndpoint.startDemoDeviceServer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shutdown() {
|
fun shutdown() {
|
||||||
logger.info { "Shutting down..." }
|
logger.info { "Shutting down..." }
|
||||||
visualizer?.stop(1000,5000)
|
visualizer?.stop(1000, 5000)
|
||||||
logger.info { "Visualization server stopped" }
|
logger.info { "Visualization server stopped" }
|
||||||
magixServer?.stop(1000, 5000)
|
magixServer?.stop(1000, 5000)
|
||||||
logger.info { "Magix server stopped" }
|
logger.info { "Magix server stopped" }
|
||||||
@ -104,10 +107,10 @@ class DemoControllerView : View(title = " Demo controller remote") {
|
|||||||
button("Show plots") {
|
button("Show plots") {
|
||||||
useMaxWidth = true
|
useMaxWidth = true
|
||||||
action {
|
action {
|
||||||
controller.magixServer?.run {
|
controller.visualizer?.run {
|
||||||
val host = "localhost"//environment.connectors.first().host
|
val host = "localhost"//environment.connectors.first().host
|
||||||
val port = environment.connectors.first().port
|
val port = environment.connectors.first().port
|
||||||
val uri = URI("http", null, host, port, "/plots", null, null)
|
val uri = URI("http", null, host, port, "/", null, null)
|
||||||
Desktop.getDesktop().browse(uri)
|
Desktop.getDesktop().browse(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
|||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
@OptIn(ExperimentalTime::class)
|
||||||
override fun DemoDevice.onStartup() {
|
override fun DemoDevice.onStartup() {
|
||||||
doRecurring(Duration.milliseconds(50)){
|
doRecurring(Duration.milliseconds(10)){
|
||||||
sin.read()
|
sin.read()
|
||||||
cos.read()
|
cos.read()
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package ru.mipt.npm.controls.demo
|
package ru.mipt.npm.controls.demo
|
||||||
|
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.features.CORS
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
import io.ktor.server.engine.ApplicationEngine
|
import io.ktor.server.engine.ApplicationEngine
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.websocket.WebSockets
|
||||||
|
import io.rsocket.kotlin.transport.ktor.server.RSocketSupport
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.html.div
|
import kotlinx.html.div
|
||||||
@ -10,8 +14,7 @@ import kotlinx.html.link
|
|||||||
import ru.mipt.npm.controls.api.DeviceMessage
|
import ru.mipt.npm.controls.api.DeviceMessage
|
||||||
import ru.mipt.npm.controls.api.PropertyChangedMessage
|
import ru.mipt.npm.controls.api.PropertyChangedMessage
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||||
import ru.mipt.npm.magix.rsocket.rSocketWithWebSockets
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.MetaItem
|
|
||||||
import space.kscience.dataforge.meta.double
|
import space.kscience.dataforge.meta.double
|
||||||
import space.kscience.plotly.layout
|
import space.kscience.plotly.layout
|
||||||
import space.kscience.plotly.models.Trace
|
import space.kscience.plotly.models.Trace
|
||||||
@ -51,85 +54,92 @@ suspend fun Trace.updateXYFrom(flow: Flow<Iterable<Pair<Double, Double>>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun startDemoDeviceServer(magixHost: String = "localhost"): ApplicationEngine = embeddedServer(CIO, 8080) {
|
suspend fun MagixEndpoint<DeviceMessage>.startDemoDeviceServer(): ApplicationEngine =
|
||||||
val sinFlow = MutableSharedFlow<MetaItem?>()// = device.sin.flow()
|
embeddedServer(CIO, 9090) {
|
||||||
val cosFlow = MutableSharedFlow<MetaItem?>()// = device.cos.flow()
|
install(WebSockets)
|
||||||
|
install(RSocketSupport)
|
||||||
|
|
||||||
launch {
|
install(CORS) {
|
||||||
val endpoint = MagixEndpoint.rSocketWithWebSockets(magixHost, DeviceMessage.serializer())
|
anyHost()
|
||||||
endpoint.subscribe().collect { magix ->
|
}
|
||||||
(magix.payload as? PropertyChangedMessage)?.let { message ->
|
|
||||||
when (message.property) {
|
val sinFlow = MutableSharedFlow<Meta?>()// = device.sin.flow()
|
||||||
"sin" -> sinFlow.emit(message.value)
|
val cosFlow = MutableSharedFlow<Meta?>()// = device.cos.flow()
|
||||||
"cos" -> cosFlow.emit(message.value)
|
|
||||||
|
launch {
|
||||||
|
subscribe().collect { magix ->
|
||||||
|
(magix.payload as? PropertyChangedMessage)?.let { message ->
|
||||||
|
when (message.property) {
|
||||||
|
"sin" -> sinFlow.emit(message.value)
|
||||||
|
"cos" -> cosFlow.emit(message.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
plotlyModule("plots").apply {
|
plotlyModule().apply {
|
||||||
updateMode = PlotlyUpdateMode.PUSH
|
updateMode = PlotlyUpdateMode.PUSH
|
||||||
updateInterval = 50
|
updateInterval = 50
|
||||||
}.page { container ->
|
}.page { container ->
|
||||||
val sinCosFlow = sinFlow.zip(cosFlow) { sin, cos ->
|
val sinCosFlow = sinFlow.zip(cosFlow) { sin, cos ->
|
||||||
sin.double!! to cos.double!!
|
sin.double!! to cos.double!!
|
||||||
}
|
}
|
||||||
link {
|
link {
|
||||||
rel = "stylesheet"
|
rel = "stylesheet"
|
||||||
href = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
|
href = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
|
||||||
attributes["integrity"] = "sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
|
attributes["integrity"] = "sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
|
||||||
attributes["crossorigin"] = "anonymous"
|
attributes["crossorigin"] = "anonymous"
|
||||||
}
|
}
|
||||||
div("row") {
|
div("row") {
|
||||||
div("col-6") {
|
div("col-6") {
|
||||||
plot(renderer = container) {
|
plot(renderer = container) {
|
||||||
layout {
|
layout {
|
||||||
title = "sin property"
|
title = "sin property"
|
||||||
xaxis.title = "point index"
|
xaxis.title = "point index"
|
||||||
yaxis.title = "sin"
|
yaxis.title = "sin"
|
||||||
|
}
|
||||||
|
trace {
|
||||||
|
launch {
|
||||||
|
val flow: Flow<Iterable<Double>> = sinFlow.mapNotNull { it.double }.windowed(100)
|
||||||
|
updateFrom(Trace.Y_AXIS, flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trace {
|
}
|
||||||
launch {
|
div("col-6") {
|
||||||
val flow: Flow<Iterable<Double>> = sinFlow.mapNotNull { it.double }.windowed(100)
|
plot(renderer = container) {
|
||||||
updateFrom(Trace.Y_AXIS, flow)
|
layout {
|
||||||
|
title = "cos property"
|
||||||
|
xaxis.title = "point index"
|
||||||
|
yaxis.title = "cos"
|
||||||
|
}
|
||||||
|
trace {
|
||||||
|
launch {
|
||||||
|
val flow: Flow<Iterable<Double>> = cosFlow.mapNotNull { it.double }.windowed(100)
|
||||||
|
updateFrom(Trace.Y_AXIS, flow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("col-6") {
|
div("row") {
|
||||||
plot(renderer = container) {
|
div("col-12") {
|
||||||
layout {
|
plot(renderer = container) {
|
||||||
title = "cos property"
|
layout {
|
||||||
xaxis.title = "point index"
|
title = "cos vs sin"
|
||||||
yaxis.title = "cos"
|
xaxis.title = "sin"
|
||||||
}
|
yaxis.title = "cos"
|
||||||
trace {
|
}
|
||||||
launch {
|
trace {
|
||||||
val flow: Flow<Iterable<Double>> = cosFlow.mapNotNull { it.double }.windowed(100)
|
name = "non-synchronized"
|
||||||
updateFrom(Trace.Y_AXIS, flow)
|
launch {
|
||||||
|
val flow: Flow<Iterable<Pair<Double, Double>>> = sinCosFlow.windowed(30)
|
||||||
|
updateXYFrom(flow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("row") {
|
}.apply { start() }
|
||||||
div("col-12") {
|
|
||||||
plot(renderer = container) {
|
|
||||||
layout {
|
|
||||||
title = "cos vs sin"
|
|
||||||
xaxis.title = "sin"
|
|
||||||
yaxis.title = "cos"
|
|
||||||
}
|
|
||||||
trace {
|
|
||||||
name = "non-synchronized"
|
|
||||||
launch {
|
|
||||||
val flow: Flow<Iterable<Pair<Double, Double>>> = sinCosFlow.windowed(30)
|
|
||||||
updateXYFrom(flow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("ru.mipt.npm.gradle.jvm")
|
id("ru.mipt.npm.gradle.jvm")
|
||||||
|
application
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -13,3 +14,7 @@ dependencies{
|
|||||||
kotlin{
|
kotlin{
|
||||||
explicitApi = null
|
explicitApi = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
application{
|
||||||
|
mainClass.set("ZmqKt")
|
||||||
|
}
|
@ -9,6 +9,8 @@ import io.rsocket.kotlin.payload.buildPayload
|
|||||||
import io.rsocket.kotlin.payload.data
|
import io.rsocket.kotlin.payload.data
|
||||||
import io.rsocket.kotlin.transport.ktor.client.RSocketSupport
|
import io.rsocket.kotlin.transport.ktor.client.RSocketSupport
|
||||||
import io.rsocket.kotlin.transport.ktor.client.rSocket
|
import io.rsocket.kotlin.transport.ktor.client.rSocket
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@ -36,7 +38,7 @@ public class RSocketMagixEndpoint<T>(
|
|||||||
val flow = rSocket.requestStream(payload)
|
val flow = rSocket.requestStream(payload)
|
||||||
return flow.map {
|
return flow.map {
|
||||||
MagixEndpoint.magixJson.decodeFromString(serializer, it.data.readText())
|
MagixEndpoint.magixJson.decodeFromString(serializer, it.data.readText())
|
||||||
}.flowOn(coroutineContext)
|
}.flowOn(coroutineContext[CoroutineDispatcher]?:Dispatchers.Unconfined)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun broadcast(message: MagixMessage<T>) {
|
override suspend fun broadcast(message: MagixMessage<T>) {
|
||||||
|
@ -2,9 +2,9 @@ package ru.mipt.npm.magix.rsocket
|
|||||||
|
|
||||||
import io.ktor.network.selector.ActorSelectorManager
|
import io.ktor.network.selector.ActorSelectorManager
|
||||||
import io.ktor.network.sockets.SocketOptions
|
import io.ktor.network.sockets.SocketOptions
|
||||||
import io.ktor.network.sockets.aSocket
|
import io.ktor.util.InternalAPI
|
||||||
import io.rsocket.kotlin.core.RSocketConnectorBuilder
|
import io.rsocket.kotlin.core.RSocketConnectorBuilder
|
||||||
import io.rsocket.kotlin.transport.ktor.clientTransport
|
import io.rsocket.kotlin.transport.ktor.TcpClientTransport
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||||
@ -14,6 +14,7 @@ import kotlin.coroutines.coroutineContext
|
|||||||
/**
|
/**
|
||||||
* Create a plain TCP based [RSocketMagixEndpoint] connected to [host] and [port]
|
* Create a plain TCP based [RSocketMagixEndpoint] connected to [host] and [port]
|
||||||
*/
|
*/
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
public suspend fun <T> MagixEndpoint.Companion.rSocketWithTcp(
|
public suspend fun <T> MagixEndpoint.Companion.rSocketWithTcp(
|
||||||
host: String,
|
host: String,
|
||||||
payloadSerializer: KSerializer<T>,
|
payloadSerializer: KSerializer<T>,
|
||||||
@ -21,7 +22,12 @@ public suspend fun <T> MagixEndpoint.Companion.rSocketWithTcp(
|
|||||||
tcpConfig: SocketOptions.TCPClientSocketOptions.() -> Unit = {},
|
tcpConfig: SocketOptions.TCPClientSocketOptions.() -> Unit = {},
|
||||||
rSocketConfig: RSocketConnectorBuilder.ConnectionConfigBuilder.() -> Unit = {},
|
rSocketConfig: RSocketConnectorBuilder.ConnectionConfigBuilder.() -> Unit = {},
|
||||||
): RSocketMagixEndpoint<T> {
|
): RSocketMagixEndpoint<T> {
|
||||||
val transport = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().clientTransport(host, port, tcpConfig)
|
val transport = TcpClientTransport(
|
||||||
|
ActorSelectorManager(Dispatchers.IO),
|
||||||
|
hostname = host,
|
||||||
|
port = port,
|
||||||
|
configure = tcpConfig
|
||||||
|
)
|
||||||
val rSocket = buildConnector(rSocketConfig).connect(transport)
|
val rSocket = buildConnector(rSocketConfig).connect(transport)
|
||||||
|
|
||||||
return RSocketMagixEndpoint(payloadSerializer, rSocket, coroutineContext)
|
return RSocketMagixEndpoint(payloadSerializer, rSocket, coroutineContext)
|
||||||
|
@ -2,12 +2,12 @@ package ru.mipt.npm.magix.server
|
|||||||
|
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.network.selector.ActorSelectorManager
|
import io.ktor.network.selector.ActorSelectorManager
|
||||||
import io.ktor.network.sockets.aSocket
|
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
import io.ktor.server.engine.ApplicationEngine
|
import io.ktor.server.engine.ApplicationEngine
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.util.InternalAPI
|
||||||
import io.rsocket.kotlin.core.RSocketServer
|
import io.rsocket.kotlin.core.RSocketServer
|
||||||
import io.rsocket.kotlin.transport.ktor.serverTransport
|
import io.rsocket.kotlin.transport.ktor.TcpServerTransport
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -20,11 +20,12 @@ import ru.mipt.npm.magix.api.MagixEndpoint.Companion.DEFAULT_MAGIX_RAW_PORT
|
|||||||
/**
|
/**
|
||||||
* Raw TCP magix server
|
* Raw TCP magix server
|
||||||
*/
|
*/
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
public fun CoroutineScope.launchMagixServerRawRSocket(
|
public fun CoroutineScope.launchMagixServerRawRSocket(
|
||||||
magixFlow: MutableSharedFlow<GenericMagixMessage>,
|
magixFlow: MutableSharedFlow<GenericMagixMessage>,
|
||||||
rawSocketPort: Int = DEFAULT_MAGIX_RAW_PORT
|
rawSocketPort: Int = DEFAULT_MAGIX_RAW_PORT
|
||||||
): Job {
|
): Job {
|
||||||
val tcpTransport = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().serverTransport(port = rawSocketPort)
|
val tcpTransport = TcpServerTransport(ActorSelectorManager(Dispatchers.IO), port = rawSocketPort)
|
||||||
val rSocketJob = RSocketServer().bind(tcpTransport, magixAcceptor(magixFlow))
|
val rSocketJob = RSocketServer().bind(tcpTransport, magixAcceptor(magixFlow))
|
||||||
coroutineContext[Job]?.invokeOnCompletion {
|
coroutineContext[Job]?.invokeOnCompletion {
|
||||||
rSocketJob.cancel()
|
rSocketJob.cancel()
|
||||||
|
@ -14,6 +14,7 @@ import ru.mipt.npm.magix.api.MagixMessage
|
|||||||
import ru.mipt.npm.magix.api.MagixMessageFilter
|
import ru.mipt.npm.magix.api.MagixMessageFilter
|
||||||
import ru.mipt.npm.magix.api.filter
|
import ru.mipt.npm.magix.api.filter
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
public class ZmqMagixEndpoint<T>(
|
public class ZmqMagixEndpoint<T>(
|
||||||
private val host: String,
|
private val host: String,
|
||||||
@ -53,7 +54,9 @@ public class ZmqMagixEndpoint<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.filter(filter).flowOn(coroutineContext) //should be flown on IO because of blocking calls
|
}.filter(filter).flowOn(
|
||||||
|
coroutineContext[CoroutineDispatcher] ?: Dispatchers.IO
|
||||||
|
) //should be flown on IO because of blocking calls
|
||||||
}
|
}
|
||||||
|
|
||||||
private val publishSocket by lazy {
|
private val publishSocket by lazy {
|
||||||
@ -71,3 +74,16 @@ public class ZmqMagixEndpoint<T>(
|
|||||||
zmqContext.close()
|
zmqContext.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public suspend fun <T> MagixEndpoint.Companion.zmq(
|
||||||
|
host: String,
|
||||||
|
payloadSerializer: KSerializer<T>,
|
||||||
|
pubPort: Int = DEFAULT_MAGIX_ZMQ_PUB_PORT,
|
||||||
|
pullPort: Int = DEFAULT_MAGIX_ZMQ_PULL_PORT,
|
||||||
|
): ZmqMagixEndpoint<T> = ZmqMagixEndpoint(
|
||||||
|
host,
|
||||||
|
payloadSerializer,
|
||||||
|
pubPort,
|
||||||
|
pullPort,
|
||||||
|
coroutineContext = coroutineContext
|
||||||
|
)
|
@ -2,19 +2,24 @@
|
|||||||
|
|
||||||
package ru.mipt.npm.devices.pimotionmaster
|
package ru.mipt.npm.devices.pimotionmaster
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.flow.transformWhile
|
import kotlinx.coroutines.flow.transformWhile
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import ru.mipt.npm.controls.api.DeviceHub
|
import ru.mipt.npm.controls.api.DeviceHub
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import ru.mipt.npm.controls.base.*
|
import ru.mipt.npm.controls.base.*
|
||||||
import ru.mipt.npm.controls.controllers.duration
|
import ru.mipt.npm.controls.controllers.duration
|
||||||
import ru.mipt.npm.controls.ports.*
|
import ru.mipt.npm.controls.ports.*
|
||||||
import space.kscience.dataforge.context.*
|
import space.kscience.dataforge.context.*
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.double
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
import space.kscience.dataforge.names.NameToken
|
import space.kscience.dataforge.names.NameToken
|
||||||
import space.kscience.dataforge.values.asValue
|
import space.kscience.dataforge.values.asValue
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
@ -23,7 +28,6 @@ import kotlin.time.Duration
|
|||||||
|
|
||||||
class PiMotionMasterDevice(
|
class PiMotionMasterDevice(
|
||||||
context: Context,
|
context: Context,
|
||||||
override val deviceName: String = "PiMotionMaster",
|
|
||||||
private val portFactory: PortFactory = KtorTcpPort,
|
private val portFactory: PortFactory = KtorTcpPort,
|
||||||
) : DeviceBase(context), DeviceHub {
|
) : DeviceBase(context), DeviceHub {
|
||||||
|
|
||||||
@ -48,7 +52,7 @@ class PiMotionMasterDevice(
|
|||||||
}
|
}
|
||||||
//Update port
|
//Update port
|
||||||
//address = portSpec.node
|
//address = portSpec.node
|
||||||
port = portFactory(portSpec.node!!, context)
|
port = portFactory(portSpec ?: Meta.EMPTY, context)
|
||||||
connected.updateLogical(true)
|
connected.updateLogical(true)
|
||||||
// connector.open()
|
// connector.open()
|
||||||
//Initialize axes
|
//Initialize axes
|
||||||
@ -61,7 +65,7 @@ class PiMotionMasterDevice(
|
|||||||
//re-define axes if needed
|
//re-define axes if needed
|
||||||
axes = ids.associateWith { Axis(it) }
|
axes = ids.associateWith { Axis(it) }
|
||||||
}
|
}
|
||||||
ids.map { it.asValue() }.asValue().asMetaItem()
|
Meta(ids.map { it.asValue() }.asValue())
|
||||||
initialize()
|
initialize()
|
||||||
failIfError()
|
failIfError()
|
||||||
}
|
}
|
||||||
@ -317,10 +321,10 @@ class PiMotionMasterDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val move by acting {
|
val move by acting {
|
||||||
val target = it.double ?: it.node["target"].double ?: error("Unacceptable target value $it")
|
val target = it.double ?: it?.get("target").double ?: error("Unacceptable target value $it")
|
||||||
closedLoop.write(true)
|
closedLoop.write(true)
|
||||||
//optionally set velocity
|
//optionally set velocity
|
||||||
it.node["velocity"].double?.let { v ->
|
it?.get("velocity").double?.let { v ->
|
||||||
velocity.write(v)
|
velocity.write(v)
|
||||||
}
|
}
|
||||||
targetPosition.write(target)
|
targetPosition.write(target)
|
||||||
@ -332,7 +336,7 @@ class PiMotionMasterDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun move(target: Double) {
|
suspend fun move(target: Double) {
|
||||||
move(target.asMetaItem())
|
move(target.asMeta())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ rootProject.name = "controls-kt"
|
|||||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
|
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
val toolsVersion = "0.10.0"
|
val toolsVersion = "0.10.2"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven("https://repo.kotlin.link")
|
maven("https://repo.kotlin.link")
|
||||||
|
Loading…
Reference in New Issue
Block a user