Add alternative device syntax

This commit is contained in:
Alexander Nozik 2021-06-25 19:57:04 +03:00
parent e182af403f
commit fe3958fd08
11 changed files with 327 additions and 131 deletions

View File

@ -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 {

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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)
/**
* A delegate that represents the logical-only state of the device
*/
public fun <T : Any> state(
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)
public operator fun <D : DeviceBySpec<D>, T : Any> D.get( override fun close() {
with(spec){ self.onShutdown() }
super.close()
}
}
public suspend fun <D : DeviceBySpec<D>, T : Any> D.getSuspend(
propertySpec: DevicePropertySpec<D, T> propertySpec: DevicePropertySpec<D, T>
): Deferred<T> = scope.async { ): T = propertySpec.read(this@getSuspend).also {
propertySpec.read(this@get).also { updateLogical(propertySpec.name, propertySpec.converter.objectToMetaItem(it))
setLogicalState(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)
} }

View File

@ -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
} }
} }

View File

@ -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)

View File

@ -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)

View File

@ -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() }

View File

@ -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
} }
} }
} }

View File

@ -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() class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
var timeScale by state(5000.0)
var sinScale by state( 1.0)
var cosScale by state(1.0)
override val scope: CoroutineScope = CoroutineScope( companion object : DeviceSpec<DemoDevice>(::DemoDevice) {
context.coroutineContext + executor.asCoroutineDispatcher() + Job(context.coroutineContext[Job]) // register virtual properties based on actual object state
) val timeScaleProperty = registerProperty(MetaConverter.double, DemoDevice::timeScale)
val sinScaleProperty = registerProperty(MetaConverter.double, DemoDevice::sinScale)
val cosScaleProperty = registerProperty(MetaConverter.double, DemoDevice::cosScale)
val timeScale: DeviceProperty by writingVirtual(5000.0.asValue()) val sin by doubleProperty {
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() val time = Instant.now()
sin(time.toEpochMilli().toDouble() / timeScaleValue) * sinScaleValue kotlin.math.sin(time.toEpochMilli().toDouble() / timeScale) * sinScale
} }
val cosScale by writingVirtual(1.0.asValue()) val cos by doubleProperty {
var cosScaleValue by cosScale.double()
val cos by readingNumber {
val time = Instant.now() val time = Instant.now()
cos(time.toEpochMilli().toDouble() / timeScaleValue) * cosScaleValue kotlin.math.cos(time.toEpochMilli().toDouble() / timeScale) * sinScale
} }
val coordinates by readingMeta { val coordinates by metaProperty {
Meta {
val time = Instant.now() val time = Instant.now()
"time" put time.toEpochMilli() "time" put time.toEpochMilli()
"x" put sin(time.toEpochMilli().toDouble() / timeScaleValue) * sinScaleValue "x" put getSuspend(sin)
"y" put cos(time.toEpochMilli().toDouble() / timeScaleValue) * cosScaleValue "y" put getSuspend(cos)
}
} }
val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
val resetScale: DeviceAction by acting { timeScale = 5000.0
timeScaleValue = 5000.0 sinScale = 1.0
sinScaleValue = 1.0 cosScale = 1.0
cosScaleValue = 1.0 null
} }
init { override fun DemoDevice.onStartup() {
sin.readEvery(Duration.seconds(0.2)) launch {
cos.readEvery(Duration.seconds(0.2)) while(isActive){
coordinates.readEvery(Duration.seconds(0.3)) delay(50)
} sin.read()
cos.read()
override fun close() { }
super.close() }
executor.shutdown() }
}
companion object : DeviceSpec<DemoDevice> {
override fun invoke(meta: Meta, context: Context): DemoDevice = DemoDevice(context)
} }
} }

View File

@ -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)
} }