Add alternative device syntax
This commit is contained in:
parent
e182af403f
commit
fe3958fd08
@ -12,10 +12,11 @@ import space.kscience.dataforge.misc.Type
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General interface describing a managed Device
|
* General interface describing a managed Device.
|
||||||
|
* Device is a supervisor scope encompassing all operations on a device. When canceled, cancels all running processes.
|
||||||
*/
|
*/
|
||||||
@Type(DEVICE_TARGET)
|
@Type(DEVICE_TARGET)
|
||||||
public interface Device : Closeable, ContextAware {
|
public interface Device : Closeable, ContextAware, CoroutineScope {
|
||||||
/**
|
/**
|
||||||
* List of supported property descriptors
|
* List of supported property descriptors
|
||||||
*/
|
*/
|
||||||
@ -27,11 +28,6 @@ public interface Device : Closeable, ContextAware {
|
|||||||
*/
|
*/
|
||||||
public val actionDescriptors: Collection<ActionDescriptor>
|
public val actionDescriptors: Collection<ActionDescriptor>
|
||||||
|
|
||||||
/**
|
|
||||||
* The supervisor scope encompassing all operations on a device. When canceled, cancels all running processes.
|
|
||||||
*/
|
|
||||||
public val scope: CoroutineScope
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of the property or throw error if property in not defined.
|
* Get the value of the property or throw error if property in not defined.
|
||||||
* Suspend if property value is not available
|
* Suspend if property value is not available
|
||||||
@ -61,7 +57,7 @@ public interface Device : Closeable, ContextAware {
|
|||||||
public suspend fun execute(action: String, argument: MetaItem? = null): MetaItem?
|
public suspend fun execute(action: String, argument: MetaItem? = null): MetaItem?
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
scope.cancel("The device is closed")
|
cancel("The device is closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
|
@ -13,6 +13,7 @@ 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.MetaItem
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
//TODO move to DataForge-core
|
//TODO move to DataForge-core
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
@ -28,7 +29,7 @@ private open class BasicReadOnlyDeviceProperty(
|
|||||||
private val getter: suspend (before: MetaItem?) -> MetaItem,
|
private val getter: suspend (before: MetaItem?) -> MetaItem,
|
||||||
) : ReadOnlyDeviceProperty {
|
) : ReadOnlyDeviceProperty {
|
||||||
|
|
||||||
override val scope: CoroutineScope get() = device.scope
|
override val scope: CoroutineScope get() = device
|
||||||
|
|
||||||
private val state: MutableStateFlow<MetaItem?> = MutableStateFlow(default)
|
private val state: MutableStateFlow<MetaItem?> = MutableStateFlow(default)
|
||||||
override val value: MetaItem? get() = state.value
|
override val value: MetaItem? get() = state.value
|
||||||
@ -107,11 +108,11 @@ private class BasicDeviceProperty(
|
|||||||
* Baseline implementation of [Device] interface
|
* Baseline implementation of [Device] interface
|
||||||
*/
|
*/
|
||||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||||
public abstract class DeviceBase(override val context: Context) : Device {
|
public abstract class DeviceBase(final override val context: Context) : Device {
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext =
|
||||||
|
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
||||||
|
|
||||||
override val scope: CoroutineScope by lazy {
|
|
||||||
CoroutineScope(context.coroutineContext + Job(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
|
||||||
@ -219,7 +220,7 @@ public abstract class DeviceBase(override val context: Context) : Device {
|
|||||||
private val block: suspend (MetaItem?) -> MetaItem?,
|
private val block: suspend (MetaItem?) -> MetaItem?,
|
||||||
) : DeviceAction {
|
) : DeviceAction {
|
||||||
override suspend fun invoke(arg: MetaItem?): MetaItem? =
|
override suspend fun invoke(arg: MetaItem?): MetaItem? =
|
||||||
withContext(scope.coroutineContext + SupervisorJob(scope.coroutineContext[Job])) {
|
withContext(coroutineContext) {
|
||||||
block(arg)
|
block(arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,16 +40,15 @@ public class DeviceManager(override val deviceName: String = "") : AbstractPlugi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DeviceSpec<D : Device> : Factory<D>
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceManager.install(name: String, factory: DeviceSpec<D>, meta: Meta = Meta.EMPTY): D {
|
public fun <D : Device> DeviceManager.install(name: String, factory: Factory<D>, meta: Meta = Meta.EMPTY): D {
|
||||||
val device = factory(meta, context)
|
val device = factory(meta, context)
|
||||||
registerDevice(NameToken(name), device)
|
registerDevice(NameToken(name), device)
|
||||||
return device
|
return device
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <D : Device> DeviceManager.installing(
|
public fun <D : Device> DeviceManager.installing(
|
||||||
factory: DeviceSpec<D>,
|
factory: Factory<D>,
|
||||||
metaBuilder: MetaBuilder.() -> Unit = {},
|
metaBuilder: MetaBuilder.() -> Unit = {},
|
||||||
): ReadOnlyProperty<Any?, D> = ReadOnlyProperty { _, property ->
|
): ReadOnlyProperty<Any?, D> = ReadOnlyProperty { _, property ->
|
||||||
val name = property.name
|
val name = property.name
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
package ru.mipt.npm.controls.properties
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
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.ActionDescriptor
|
||||||
@ -15,22 +12,28 @@ 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.MetaItem
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.properties.Delegates.observable
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param D recursive self-type for properties and actions
|
* @param D recursive self-type for properties and actions
|
||||||
*/
|
*/
|
||||||
public open class DeviceBySpec<D : DeviceBySpec<D>> : Device {
|
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||||
override var context: Context = Global
|
public val spec: DeviceSpec<D>,
|
||||||
|
context: Context = Global,
|
||||||
|
meta: Meta = Meta.EMPTY
|
||||||
|
) : Device {
|
||||||
|
override var context: Context = context
|
||||||
internal set
|
internal set
|
||||||
|
|
||||||
public var meta: Meta = Meta.EMPTY
|
public var meta: Meta = meta
|
||||||
internal set
|
internal set
|
||||||
|
|
||||||
public var properties: Map<String, DevicePropertySpec<D, *>> = emptyMap()
|
public val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
||||||
internal set
|
public val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
||||||
|
|
||||||
public var actions: Map<String, DeviceActionSpec<D, *, *>> = emptyMap()
|
|
||||||
internal set
|
|
||||||
|
|
||||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
override val propertyDescriptors: Collection<PropertyDescriptor>
|
||||||
get() = properties.values.map { it.descriptor }
|
get() = properties.values.map { it.descriptor }
|
||||||
@ -38,7 +41,9 @@ public open class DeviceBySpec<D : DeviceBySpec<D>> : Device {
|
|||||||
override val actionDescriptors: Collection<ActionDescriptor>
|
override val actionDescriptors: Collection<ActionDescriptor>
|
||||||
get() = actions.values.map { it.descriptor }
|
get() = actions.values.map { it.descriptor }
|
||||||
|
|
||||||
override val scope: CoroutineScope get() = context
|
override val coroutineContext: CoroutineContext by lazy {
|
||||||
|
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
||||||
|
}
|
||||||
|
|
||||||
private val logicalState: HashMap<String, MetaItem?> = HashMap()
|
private val logicalState: HashMap<String, MetaItem?> = HashMap()
|
||||||
|
|
||||||
@ -54,7 +59,7 @@ public open class DeviceBySpec<D : DeviceBySpec<D>> : Device {
|
|||||||
|
|
||||||
private val stateLock = Mutex()
|
private val stateLock = Mutex()
|
||||||
|
|
||||||
internal suspend fun setLogicalState(propertyName: String, value: MetaItem?) {
|
internal suspend fun updateLogical(propertyName: String, value: MetaItem?) {
|
||||||
if (value != logicalState[propertyName]) {
|
if (value != logicalState[propertyName]) {
|
||||||
stateLock.withLock {
|
stateLock.withLock {
|
||||||
logicalState[propertyName] = value
|
logicalState[propertyName] = value
|
||||||
@ -66,12 +71,13 @@ public open class DeviceBySpec<D : DeviceBySpec<D>> : Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force read physical value and push an update if it is changed
|
* 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
|
||||||
*/
|
*/
|
||||||
public suspend fun readProperty(propertyName: String): MetaItem {
|
public suspend fun readProperty(propertyName: String): MetaItem {
|
||||||
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")
|
||||||
setLogicalState(propertyName, newValue)
|
updateLogical(propertyName, newValue)
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,31 +90,58 @@ public open class DeviceBySpec<D : DeviceBySpec<D>> : Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setProperty(propertyName: String, value: MetaItem) {
|
override suspend fun setProperty(propertyName: String, value: MetaItem): 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)
|
||||||
invalidateProperty(propertyName)
|
invalidateProperty(propertyName)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
setLogicalState(propertyName, value)
|
updateLogical(propertyName, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(action: String, argument: MetaItem?): MetaItem? =
|
override suspend fun execute(action: String, argument: MetaItem?): MetaItem? =
|
||||||
actions[action]?.executeItem(self, argument)
|
actions[action]?.executeItem(self, argument)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public operator fun <D : DeviceBySpec<D>, T : Any> D.get(
|
/**
|
||||||
propertySpec: DevicePropertySpec<D, T>
|
* A delegate that represents the logical-only state of the device
|
||||||
): Deferred<T> = scope.async {
|
*/
|
||||||
propertySpec.read(this@get).also {
|
public fun <T : Any> state(
|
||||||
setLogicalState(propertySpec.name, propertySpec.converter.objectToMetaItem(it))
|
converter: MetaConverter<T>,
|
||||||
|
initialValue: T,
|
||||||
|
): ReadWriteProperty<D, T> = observable(initialValue) { property: KProperty<*>, oldValue: T, newValue: T ->
|
||||||
|
if (oldValue != newValue) {
|
||||||
|
launch {
|
||||||
|
invalidateProperty(property.name)
|
||||||
|
_propertyFlow.emit(property.name to converter.objectToMetaItem(newValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public suspend fun <T : Any> DevicePropertySpec<D, T>.read(): T = read(self)
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
with(spec){ self.onShutdown() }
|
||||||
|
super.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public suspend fun <D : DeviceBySpec<D>, T : Any> D.getSuspend(
|
||||||
|
propertySpec: DevicePropertySpec<D, T>
|
||||||
|
): T = propertySpec.read(this@getSuspend).also {
|
||||||
|
updateLogical(propertySpec.name, propertySpec.converter.objectToMetaItem(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>, T : Any> D.getAsync(
|
||||||
|
propertySpec: DevicePropertySpec<D, T>
|
||||||
|
): Deferred<T> = async {
|
||||||
|
getSuspend(propertySpec)
|
||||||
|
}
|
||||||
|
|
||||||
public operator fun <D : DeviceBySpec<D>, T : Any> D.set(propertySpec: WritableDevicePropertySpec<D, T>, value: T) {
|
public operator fun <D : DeviceBySpec<D>, T : Any> D.set(propertySpec: WritableDevicePropertySpec<D, T>, value: T) {
|
||||||
scope.launch {
|
launch {
|
||||||
propertySpec.write(this@set, value)
|
propertySpec.write(this@set, value)
|
||||||
invalidateProperty(propertySpec.name)
|
invalidateProperty(propertySpec.name)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
package ru.mipt.npm.controls.properties
|
||||||
|
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.withContext
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
import ru.mipt.npm.controls.api.ActionDescriptor
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
@ -9,13 +9,58 @@ import space.kscience.dataforge.meta.Meta
|
|||||||
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
|
||||||
|
import kotlin.reflect.KMutableProperty1
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
|
||||||
public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
||||||
private val buildDevice: () -> D
|
private val buildDevice: () -> D
|
||||||
) : Factory<D> {
|
) : Factory<D> {
|
||||||
private val deviceProperties = HashMap<String, DevicePropertySpec<D, *>>()
|
private val _properties = HashMap<String, DevicePropertySpec<D, *>>()
|
||||||
private val deviceActions = HashMap<String, DeviceActionSpec<D, *, *>>()
|
public val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
|
||||||
|
|
||||||
|
private val _actions = HashMap<String, DeviceActionSpec<D, *, *>>()
|
||||||
|
public val actions: Map<String, DeviceActionSpec<D, *, *>> get() = _actions
|
||||||
|
|
||||||
|
public fun <T : Any> registerProperty(deviceProperty: DevicePropertySpec<D, T>): DevicePropertySpec<D, T> {
|
||||||
|
_properties[deviceProperty.name] = deviceProperty
|
||||||
|
return deviceProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T : Any> registerProperty(
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
readOnlyProperty: KProperty1<D, T>,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
|
): DevicePropertySpec<D, T> {
|
||||||
|
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||||
|
override val name: String = readOnlyProperty.name
|
||||||
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
||||||
|
override val converter: MetaConverter<T> = converter
|
||||||
|
override suspend fun read(device: D): T =
|
||||||
|
withContext(device.coroutineContext) { readOnlyProperty.get(device) }
|
||||||
|
}
|
||||||
|
return registerProperty(deviceProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T : Any> registerProperty(
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
readWriteProperty: KMutableProperty1<D, T>,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
|
): WritableDevicePropertySpec<D, T> {
|
||||||
|
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||||
|
override val name: String = readWriteProperty.name
|
||||||
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
||||||
|
override val converter: MetaConverter<T> = converter
|
||||||
|
override suspend fun read(device: D): T =
|
||||||
|
withContext(device.coroutineContext) { readWriteProperty.get(device) }
|
||||||
|
|
||||||
|
override suspend fun write(device: D, value: T) = withContext(device.coroutineContext) {
|
||||||
|
readWriteProperty.set(device, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerProperty(deviceProperty)
|
||||||
|
return deviceProperty
|
||||||
|
}
|
||||||
|
|
||||||
public fun <T : Any> property(
|
public fun <T : Any> property(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
@ -30,15 +75,14 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
|
|
||||||
override suspend fun read(device: D): T = device.read()
|
override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() }
|
||||||
}
|
}
|
||||||
deviceProperties[propertyName] = deviceProperty
|
_properties[propertyName] = deviceProperty
|
||||||
ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>> { _, _ ->
|
ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>> { _, _ ->
|
||||||
deviceProperty
|
deviceProperty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Any> property(
|
public fun <T : Any> property(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
@ -53,19 +97,24 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
|
|
||||||
override suspend fun read(device: D): T = device.read()
|
override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() }
|
||||||
|
|
||||||
override suspend fun write(device: D, value: T) {
|
override suspend fun write(device: D, value: T) = withContext(device.coroutineContext) {
|
||||||
device.write(value)
|
device.write(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deviceProperties[propertyName] = deviceProperty
|
_properties[propertyName] = deviceProperty
|
||||||
ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>> { _, _ ->
|
ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>> { _, _ ->
|
||||||
deviceProperty
|
deviceProperty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public fun <I : Any, O : Any> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O> {
|
||||||
|
_actions[deviceAction.name] = deviceAction
|
||||||
|
return deviceAction
|
||||||
|
}
|
||||||
|
|
||||||
public fun <I : Any, O : Any> action(
|
public fun <I : Any, O : Any> action(
|
||||||
inputConverter: MetaConverter<I>,
|
inputConverter: MetaConverter<I>,
|
||||||
outputConverter: MetaConverter<O>,
|
outputConverter: MetaConverter<O>,
|
||||||
@ -82,21 +131,30 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
override val inputConverter: MetaConverter<I> = inputConverter
|
override val inputConverter: MetaConverter<I> = inputConverter
|
||||||
override val outputConverter: MetaConverter<O> = outputConverter
|
override val outputConverter: MetaConverter<O> = outputConverter
|
||||||
|
|
||||||
override suspend fun execute(device: D, input: I?): O? {
|
override suspend fun execute(device: D, input: I?): O? = withContext(device.coroutineContext) {
|
||||||
return device.execute(input)
|
device.execute(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deviceActions[actionName] = deviceAction
|
_actions[actionName] = deviceAction
|
||||||
ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>> { _, _ ->
|
ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>> { _, _ ->
|
||||||
deviceAction
|
deviceAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function is executed right after device initialization is finished
|
||||||
|
*/
|
||||||
|
public open fun D.onStartup(){}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function is executed before device is shut down
|
||||||
|
*/
|
||||||
|
public open fun D.onShutdown(){}
|
||||||
|
|
||||||
|
|
||||||
override fun invoke(meta: Meta, context: Context): D = buildDevice().apply {
|
override fun invoke(meta: Meta, context: Context): D = buildDevice().apply {
|
||||||
this.context = context
|
this.context = context
|
||||||
this.meta = meta
|
this.meta = meta
|
||||||
this.properties = deviceProperties
|
onStartup()
|
||||||
this.actions = deviceActions
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package ru.mipt.npm.controls.properties
|
||||||
|
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> D.state(
|
||||||
|
initialValue: Double,
|
||||||
|
): ReadWriteProperty<D, Double> = state(MetaConverter.double, initialValue)
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> D.state(
|
||||||
|
initialValue: Number,
|
||||||
|
): ReadWriteProperty<D, Number> = state(MetaConverter.number, initialValue)
|
@ -0,0 +1,105 @@
|
|||||||
|
package ru.mipt.npm.controls.properties
|
||||||
|
|
||||||
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
|
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 kotlin.properties.PropertyDelegateProvider
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
|
||||||
|
//read only delegates
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.booleanProperty(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> Boolean
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> =
|
||||||
|
property(MetaConverter.boolean, name, descriptorBuilder, read)
|
||||||
|
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.numberProperty(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> Number
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> =
|
||||||
|
property(MetaConverter.number, name, descriptorBuilder, read)
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.doubleProperty(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> Double
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> =
|
||||||
|
property(MetaConverter.double, name, descriptorBuilder, read)
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.stringProperty(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> String
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> =
|
||||||
|
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(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> Meta
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> =
|
||||||
|
property(MetaConverter.meta, name, descriptorBuilder, read)
|
||||||
|
|
||||||
|
//read-write delegates
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.booleanProperty(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> Boolean,
|
||||||
|
write: suspend D.(Boolean) -> Unit
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
||||||
|
property(MetaConverter.boolean, name, descriptorBuilder, read, write)
|
||||||
|
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.numberProperty(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> Number,
|
||||||
|
write: suspend D.(Number) -> Unit
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Number>>> =
|
||||||
|
property(MetaConverter.number, name, descriptorBuilder, read, write)
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.doubleProperty(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> Double,
|
||||||
|
write: suspend D.(Double) -> Unit
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Double>>> =
|
||||||
|
property(MetaConverter.double, name, descriptorBuilder, read, write)
|
||||||
|
|
||||||
|
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.stringProperty(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> String,
|
||||||
|
write: suspend D.(String) -> Unit
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
||||||
|
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(
|
||||||
|
name: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
read: suspend D.() -> Meta,
|
||||||
|
write: suspend D.(Meta) -> Unit
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Meta>>> =
|
||||||
|
property(MetaConverter.meta, name, descriptorBuilder, read, write)
|
@ -0,0 +1,13 @@
|
|||||||
|
package ru.mipt.npm.controls.properties
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import kotlin.reflect.KFunction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocking property get call
|
||||||
|
*/
|
||||||
|
public operator fun <D : DeviceBySpec<D>, T : Any> D.get(
|
||||||
|
propertySpec: DevicePropertySpec<D, T>
|
||||||
|
): T = runBlocking { getAsync(propertySpec).await() }
|
@ -95,9 +95,9 @@ class DemoControllerView : View(title = " Demo controller remote") {
|
|||||||
useMaxWidth = true
|
useMaxWidth = true
|
||||||
action {
|
action {
|
||||||
controller.device?.apply {
|
controller.device?.apply {
|
||||||
timeScaleValue = timeScaleSlider.value
|
timeScale = timeScaleSlider.value
|
||||||
sinScaleValue = xScaleSlider.value
|
sinScale = xScaleSlider.value
|
||||||
cosScaleValue = yScaleSlider.value
|
cosScale = yScaleSlider.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,73 +1,59 @@
|
|||||||
package ru.mipt.npm.controls.demo
|
package ru.mipt.npm.controls.demo
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.controls.base.*
|
import ru.mipt.npm.controls.properties.*
|
||||||
import ru.mipt.npm.controls.controllers.DeviceSpec
|
|
||||||
import ru.mipt.npm.controls.controllers.double
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.values.asValue
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import kotlin.math.cos
|
|
||||||
import kotlin.math.sin
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.ExperimentalTime
|
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
|
||||||
class DemoDevice(context: Context) : DeviceBase(context) {
|
|
||||||
|
|
||||||
private val executor = Executors.newSingleThreadExecutor()
|
|
||||||
|
|
||||||
override val scope: CoroutineScope = CoroutineScope(
|
|
||||||
context.coroutineContext + executor.asCoroutineDispatcher() + Job(context.coroutineContext[Job])
|
|
||||||
)
|
|
||||||
|
|
||||||
val timeScale: DeviceProperty by writingVirtual(5000.0.asValue())
|
|
||||||
var timeScaleValue by timeScale.double()
|
|
||||||
|
|
||||||
val sinScale by writingVirtual(1.0.asValue())
|
|
||||||
var sinScaleValue by sinScale.double()
|
|
||||||
val sin: TypedReadOnlyDeviceProperty<Number> by readingNumber {
|
|
||||||
val time = Instant.now()
|
|
||||||
sin(time.toEpochMilli().toDouble() / timeScaleValue) * sinScaleValue
|
|
||||||
}
|
|
||||||
|
|
||||||
val cosScale by writingVirtual(1.0.asValue())
|
|
||||||
var cosScaleValue by cosScale.double()
|
|
||||||
val cos by readingNumber {
|
|
||||||
val time = Instant.now()
|
|
||||||
cos(time.toEpochMilli().toDouble() / timeScaleValue) * cosScaleValue
|
|
||||||
}
|
|
||||||
|
|
||||||
val coordinates by readingMeta {
|
|
||||||
val time = Instant.now()
|
|
||||||
"time" put time.toEpochMilli()
|
|
||||||
"x" put sin(time.toEpochMilli().toDouble() / timeScaleValue) * sinScaleValue
|
|
||||||
"y" put cos(time.toEpochMilli().toDouble() / timeScaleValue) * cosScaleValue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val resetScale: DeviceAction by acting {
|
class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
||||||
timeScaleValue = 5000.0
|
var timeScale by state(5000.0)
|
||||||
sinScaleValue = 1.0
|
var sinScale by state( 1.0)
|
||||||
cosScaleValue = 1.0
|
var cosScale by state(1.0)
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
companion object : DeviceSpec<DemoDevice>(::DemoDevice) {
|
||||||
sin.readEvery(Duration.seconds(0.2))
|
// register virtual properties based on actual object state
|
||||||
cos.readEvery(Duration.seconds(0.2))
|
val timeScaleProperty = registerProperty(MetaConverter.double, DemoDevice::timeScale)
|
||||||
coordinates.readEvery(Duration.seconds(0.3))
|
val sinScaleProperty = registerProperty(MetaConverter.double, DemoDevice::sinScale)
|
||||||
}
|
val cosScaleProperty = registerProperty(MetaConverter.double, DemoDevice::cosScale)
|
||||||
|
|
||||||
override fun close() {
|
val sin by doubleProperty {
|
||||||
super.close()
|
val time = Instant.now()
|
||||||
executor.shutdown()
|
kotlin.math.sin(time.toEpochMilli().toDouble() / timeScale) * sinScale
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : DeviceSpec<DemoDevice> {
|
val cos by doubleProperty {
|
||||||
override fun invoke(meta: Meta, context: Context): DemoDevice = DemoDevice(context)
|
val time = Instant.now()
|
||||||
|
kotlin.math.cos(time.toEpochMilli().toDouble() / timeScale) * sinScale
|
||||||
|
}
|
||||||
|
|
||||||
|
val coordinates by metaProperty {
|
||||||
|
Meta {
|
||||||
|
val time = Instant.now()
|
||||||
|
"time" put time.toEpochMilli()
|
||||||
|
"x" put getSuspend(sin)
|
||||||
|
"y" put getSuspend(cos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
|
||||||
|
timeScale = 5000.0
|
||||||
|
sinScale = 1.0
|
||||||
|
cosScale = 1.0
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun DemoDevice.onStartup() {
|
||||||
|
launch {
|
||||||
|
while(isActive){
|
||||||
|
delay(50)
|
||||||
|
sin.read()
|
||||||
|
cos.read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,7 +11,6 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
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.DeviceSpec
|
|
||||||
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.*
|
||||||
@ -28,10 +27,6 @@ class PiMotionMasterDevice(
|
|||||||
private val portFactory: PortFactory = KtorTcpPort,
|
private val portFactory: PortFactory = KtorTcpPort,
|
||||||
) : DeviceBase(context), DeviceHub {
|
) : DeviceBase(context), DeviceHub {
|
||||||
|
|
||||||
override val scope: CoroutineScope = CoroutineScope(
|
|
||||||
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
|
||||||
)
|
|
||||||
|
|
||||||
private var port: Port? = null
|
private var port: Port? = null
|
||||||
//TODO make proxy work
|
//TODO make proxy work
|
||||||
//PortProxy { portFactory(address ?: error("The device is not connected"), context) }
|
//PortProxy { portFactory(address ?: error("The device is not connected"), context) }
|
||||||
@ -151,11 +146,10 @@ class PiMotionMasterDevice(
|
|||||||
withTimeout(timeoutValue) {
|
withTimeout(timeoutValue) {
|
||||||
sendCommandInternal(command, *arguments)
|
sendCommandInternal(command, *arguments)
|
||||||
val phrases = port?.receiving()?.withDelimiter("\n") ?: error("Not connected to device")
|
val phrases = port?.receiving()?.withDelimiter("\n") ?: error("Not connected to device")
|
||||||
val list = phrases.transformWhile { line ->
|
phrases.transformWhile { line ->
|
||||||
emit(line)
|
emit(line)
|
||||||
line.endsWith(" \n")
|
line.endsWith(" \n")
|
||||||
}.toList()
|
}.toList()
|
||||||
list
|
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.warn { "Error during PIMotionMaster request. Requesting error code." }
|
logger.warn { "Error during PIMotionMaster request. Requesting error code." }
|
||||||
@ -204,7 +198,6 @@ class PiMotionMasterDevice(
|
|||||||
)
|
)
|
||||||
|
|
||||||
inner class Axis(val axisId: String) : DeviceBase(context) {
|
inner class Axis(val axisId: String) : DeviceBase(context) {
|
||||||
override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope
|
|
||||||
|
|
||||||
private suspend fun readAxisBoolean(command: String): Boolean =
|
private suspend fun readAxisBoolean(command: String): Boolean =
|
||||||
requestAndParse(command, axisId)[axisId]?.toIntOrNull()
|
requestAndParse(command, axisId)[axisId]?.toIntOrNull()
|
||||||
@ -343,7 +336,7 @@ class PiMotionMasterDevice(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : DeviceSpec<PiMotionMasterDevice> {
|
companion object : Factory<PiMotionMasterDevice> {
|
||||||
override fun invoke(meta: Meta, context: Context): PiMotionMasterDevice = PiMotionMasterDevice(context)
|
override fun invoke(meta: Meta, context: Context): PiMotionMasterDevice = PiMotionMasterDevice(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user