Refactor delegated properties

This commit is contained in:
Alexander Nozik 2020-10-10 17:21:28 +03:00
parent 4b9f535002
commit eb3121aed4
11 changed files with 433 additions and 145 deletions

View File

@ -1,10 +1,14 @@
package hep.dataforge.control.base package hep.dataforge.control.base
import hep.dataforge.control.api.ActionDescriptor import hep.dataforge.control.api.ActionDescriptor
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.MetaItem
import hep.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: MetaItem<*>? = null): MetaItem<*>?
} }
public suspend operator fun DeviceAction.invoke(meta: Meta): MetaItem<*>? = invoke(meta.asMetaItem())

View File

@ -0,0 +1,54 @@
package hep.dataforge.control.base
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.transformations.MetaConverter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* A type-safe wrapper on top of read-only property
*/
public open class TypedReadOnlyDeviceProperty<T : Any>(
private val property: ReadOnlyDeviceProperty,
protected val converter: MetaConverter<T>,
) : ReadOnlyDeviceProperty by property {
public fun updateLogical(obj: T) {
property.updateLogical(converter.objectToMetaItem(obj))
}
public open val typedValue: T? get() = value?.let { converter.itemToObject(it) }
public suspend fun readTyped(force: Boolean = false): T = converter.itemToObject(read(force))
public fun flowTyped(): Flow<T?> = flow().map { it?.let { converter.itemToObject(it) } }
}
/**
* A type-safe wrapper for a read-write device property
*/
public class TypedDeviceProperty<T : Any>(
private val property: DeviceProperty,
converter: MetaConverter<T>,
) : TypedReadOnlyDeviceProperty<T>(property, converter), DeviceProperty {
// override var value: MetaItem<*>?
// get() = property.value
// set(arg) {
// property.value = arg
// }
public override var typedValue: T?
get() = value?.let { converter.itemToObject(it) }
set(arg) {
property.value = arg?.let { converter.objectToMetaItem(arg) }
}
override suspend fun write(item: MetaItem<*>) {
property.write(item)
}
public suspend fun write(obj: T) {
property.write(converter.objectToMetaItem(obj))
}
}

View File

@ -2,6 +2,7 @@ package hep.dataforge.control.base
import hep.dataforge.control.api.PropertyDescriptor import hep.dataforge.control.api.PropertyDescriptor
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.meta.transformations.MetaConverter
import hep.dataforge.values.Null import hep.dataforge.values.Null
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
@ -9,13 +10,22 @@ import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
private fun <D : DeviceBase> D.provideProperty(): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = private fun <D : DeviceBase> D.provideProperty(name: String): ReadOnlyProperty<D, ReadOnlyDeviceProperty> =
ReadOnlyProperty { _: D, property: KProperty<*> -> ReadOnlyProperty { _: D, _: KProperty<*> ->
val name = property.name return@ReadOnlyProperty properties.getValue(name)
return@ReadOnlyProperty properties[name]!!
} }
private fun <D : DeviceBase, T : Any> D.provideProperty(
name: String,
converter: MetaConverter<T>,
): ReadOnlyProperty<D, TypedReadOnlyDeviceProperty<T>> =
ReadOnlyProperty { _: D, _: KProperty<*> ->
return@ReadOnlyProperty TypedReadOnlyDeviceProperty(properties.getValue(name), converter)
}
public typealias ReadOnlyPropertyDelegate = ReadOnlyProperty<DeviceBase, ReadOnlyDeviceProperty> public typealias ReadOnlyPropertyDelegate = ReadOnlyProperty<DeviceBase, ReadOnlyDeviceProperty>
public typealias TypedReadOnlyPropertyDelegate<T> = ReadOnlyProperty<DeviceBase, TypedReadOnlyDeviceProperty<T>>
private class ReadOnlyDevicePropertyProvider<D : DeviceBase>( private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
val owner: D, val owner: D,
@ -27,7 +37,22 @@ private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate { override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate {
val name = property.name val name = property.name
owner.newReadOnlyProperty(name, default, descriptorBuilder, getter) owner.newReadOnlyProperty(name, default, descriptorBuilder, getter)
return owner.provideProperty() return owner.provideProperty(name)
}
}
private class TypedReadOnlyDevicePropertyProvider<D : DeviceBase, T : Any>(
val owner: D,
val default: MetaItem<*>?,
val converter: MetaConverter<T>,
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
) : PropertyDelegateProvider<D, TypedReadOnlyPropertyDelegate<T>> {
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedReadOnlyPropertyDelegate<T> {
val name = property.name
owner.newReadOnlyProperty(name, default, descriptorBuilder, getter)
return owner.provideProperty(name, converter)
} }
} }
@ -57,9 +82,10 @@ public fun DeviceBase.readingNumber(
default: Number? = null, default: Number? = null,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
getter: suspend () -> Number, getter: suspend () -> Number,
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider( ): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Number>> = TypedReadOnlyDevicePropertyProvider(
this, this,
default?.let { MetaItem.ValueItem(it.asValue()) }, default?.let { MetaItem.ValueItem(it.asValue()) },
MetaConverter.number,
descriptorBuilder, descriptorBuilder,
getter = { getter = {
val number = getter() val number = getter()
@ -71,9 +97,10 @@ public fun DeviceBase.readingString(
default: String? = null, default: String? = null,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
getter: suspend () -> String, getter: suspend () -> String,
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider( ): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<String>> = TypedReadOnlyDevicePropertyProvider(
this, this,
default?.let { MetaItem.ValueItem(it.asValue()) }, default?.let { MetaItem.ValueItem(it.asValue()) },
MetaConverter.string,
descriptorBuilder, descriptorBuilder,
getter = { getter = {
val number = getter() val number = getter()
@ -85,9 +112,10 @@ public fun DeviceBase.readingBoolean(
default: Boolean? = null, default: Boolean? = null,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
getter: suspend () -> Boolean, getter: suspend () -> Boolean,
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider( ): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Boolean>> = TypedReadOnlyDevicePropertyProvider(
this, this,
default?.let { MetaItem.ValueItem(it.asValue()) }, default?.let { MetaItem.ValueItem(it.asValue()) },
MetaConverter.boolean,
descriptorBuilder, descriptorBuilder,
getter = { getter = {
val boolean = getter() val boolean = getter()
@ -99,22 +127,31 @@ public fun DeviceBase.readingMeta(
default: Meta? = null, default: Meta? = null,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
getter: suspend MetaBuilder.() -> Unit, getter: suspend MetaBuilder.() -> Unit,
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider( ): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Meta>> = TypedReadOnlyDevicePropertyProvider(
this, this,
default?.let { MetaItem.NodeItem(it) }, default?.let { MetaItem.NodeItem(it) },
MetaConverter.meta,
descriptorBuilder, descriptorBuilder,
getter = { getter = {
MetaItem.NodeItem(MetaBuilder().apply { getter() }) MetaItem.NodeItem(MetaBuilder().apply { getter() })
} }
) )
private fun DeviceBase.provideMutableProperty(): ReadOnlyProperty<DeviceBase, DeviceProperty> = private fun DeviceBase.provideMutableProperty(name: String): ReadOnlyProperty<DeviceBase, DeviceProperty> =
ReadOnlyProperty { _: DeviceBase, property: KProperty<*> -> ReadOnlyProperty { _: DeviceBase, _: KProperty<*> ->
val name = property.name
return@ReadOnlyProperty properties[name] as DeviceProperty return@ReadOnlyProperty properties[name] as DeviceProperty
} }
private fun <T : Any> DeviceBase.provideMutableProperty(
name: String,
converter: MetaConverter<T>,
): ReadOnlyProperty<DeviceBase, TypedDeviceProperty<T>> =
ReadOnlyProperty { _: DeviceBase, _: KProperty<*> ->
return@ReadOnlyProperty TypedDeviceProperty(properties[name] as DeviceProperty, converter)
}
public typealias PropertyDelegate = ReadOnlyProperty<DeviceBase, DeviceProperty> public typealias PropertyDelegate = ReadOnlyProperty<DeviceBase, DeviceProperty>
public typealias TypedPropertyDelegate<T> = ReadOnlyProperty<DeviceBase, TypedDeviceProperty<T>>
private class DevicePropertyProvider<D : DeviceBase>( private class DevicePropertyProvider<D : DeviceBase>(
val owner: D, val owner: D,
@ -127,7 +164,23 @@ private class DevicePropertyProvider<D : DeviceBase>(
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate { override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate {
val name = property.name val name = property.name
owner.newMutableProperty(name, default, descriptorBuilder, getter, setter) owner.newMutableProperty(name, default, descriptorBuilder, getter, setter)
return owner.provideMutableProperty() return owner.provideMutableProperty(name)
}
}
private class TypedDevicePropertyProvider<D : DeviceBase, T : Any>(
val owner: D,
val default: MetaItem<*>?,
val converter: MetaConverter<T>,
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?,
) : PropertyDelegateProvider<D, TypedPropertyDelegate<T>> {
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedPropertyDelegate<T> {
val name = property.name
owner.newMutableProperty(name, default, descriptorBuilder, getter, setter)
return owner.provideMutableProperty(name, converter)
} }
} }
@ -164,11 +217,21 @@ public fun DeviceBase.writingVirtual(
setter = { _, newItem -> newItem } setter = { _, newItem -> newItem }
) )
public fun DeviceBase.writingVirtual(
default: Meta,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
MetaItem.NodeItem(default),
descriptorBuilder,
getter = { it ?: MetaItem.NodeItem(default) },
setter = { _, newItem -> newItem }
)
public fun <D : DeviceBase> D.writingDouble( public fun <D : DeviceBase> D.writingDouble(
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
getter: suspend (Double) -> Double, getter: suspend (Double) -> Double,
setter: suspend (oldValue: Double?, newValue: Double) -> Double?, setter: suspend (oldValue: Double?, newValue: Double) -> Double?,
): PropertyDelegateProvider<D, PropertyDelegate> { ): PropertyDelegateProvider<D, TypedPropertyDelegate<Double>> {
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = { val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue()) MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue())
} }
@ -177,9 +240,10 @@ public fun <D : DeviceBase> D.writingDouble(
setter(oldValue.double, newValue.double ?: Double.NaN)?.asMetaItem() setter(oldValue.double, newValue.double ?: Double.NaN)?.asMetaItem()
} }
return DevicePropertyProvider( return TypedDevicePropertyProvider(
this, this,
MetaItem.ValueItem(Double.NaN.asValue()), MetaItem.ValueItem(Double.NaN.asValue()),
MetaConverter.double,
descriptorBuilder, descriptorBuilder,
innerGetter, innerGetter,
innerSetter innerSetter
@ -190,7 +254,7 @@ public fun <D : DeviceBase> D.writingBoolean(
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
getter: suspend (Boolean?) -> Boolean, getter: suspend (Boolean?) -> Boolean,
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?, setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?,
): PropertyDelegateProvider<D, PropertyDelegate> { ): PropertyDelegateProvider<D, TypedPropertyDelegate<Boolean>> {
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = { val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
MetaItem.ValueItem(getter(it.boolean).asValue()) MetaItem.ValueItem(getter(it.boolean).asValue())
} }
@ -200,9 +264,10 @@ public fun <D : DeviceBase> D.writingBoolean(
?.asMetaItem() ?.asMetaItem()
} }
return DevicePropertyProvider( return TypedDevicePropertyProvider(
this, this,
MetaItem.ValueItem(Null), MetaItem.ValueItem(Null),
MetaConverter.boolean,
descriptorBuilder, descriptorBuilder,
innerGetter, innerGetter,
innerSetter innerSetter

View File

@ -1,6 +1,27 @@
package hep.dataforge.control.base package hep.dataforge.control.base
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.*
import hep.dataforge.meta.transformations.MetaConverter
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
import hep.dataforge.values.double
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.toDuration
public fun Double.asMetaItem(): MetaItem.ValueItem = MetaItem.ValueItem(asValue()) public fun Double.asMetaItem(): MetaItem.ValueItem = MetaItem.ValueItem(asValue())
//TODO to be moved to DF
public object DurationConverter : MetaConverter<Duration> {
override fun itemToObject(item: MetaItem<*>): Duration = when (item) {
is MetaItem.NodeItem -> {
val unit: DurationUnit = item.node["unit"].enum<DurationUnit>() ?: DurationUnit.SECONDS
val value = item.node[Meta.VALUE_KEY].double ?: error("No value present for Duration")
value.toDuration(unit)
}
is MetaItem.ValueItem -> item.value.double.toDuration(DurationUnit.SECONDS)
}
override fun objectToMetaItem(obj: Duration): MetaItem<*> = obj.toDouble(DurationUnit.SECONDS).asMetaItem()
}
public val MetaConverter.Companion.duration: MetaConverter<Duration> get() = DurationConverter

View File

@ -1,14 +1,13 @@
package hep.dataforge.control.controllers package hep.dataforge.control.controllers
import hep.dataforge.context.AbstractPlugin import hep.dataforge.context.*
import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.control.api.Device import hep.dataforge.control.api.Device
import hep.dataforge.control.api.DeviceHub import hep.dataforge.control.api.DeviceHub
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KClass import kotlin.reflect.KClass
public class DeviceManager : AbstractPlugin(), DeviceHub { public class DeviceManager : AbstractPlugin(), DeviceHub {
@ -38,6 +37,21 @@ public class DeviceManager : AbstractPlugin(), DeviceHub {
} }
} }
public interface DeviceFactory<D : Device> : Factory<D>
public val Context.devices: DeviceManager get() = plugins.fetch(DeviceManager) public val Context.devices: DeviceManager get() = plugins.fetch(DeviceManager)
public fun <D : Device> DeviceManager.install(name: String, factory: DeviceFactory<D>, meta: Meta = Meta.EMPTY): D {
val device = factory(meta, context)
registerDevice(NameToken(name), device)
return device
}
public fun <D : Device> DeviceManager.installing(
factory: DeviceFactory<D>,
metaBuilder: MetaBuilder.() -> Unit = {},
): ReadOnlyProperty<Any?, D> = ReadOnlyProperty { _, property ->
val name = property.name
install(name, factory, Meta(metaBuilder))
}

View File

@ -1,71 +0,0 @@
package hep.dataforge.control.controllers
import hep.dataforge.control.base.DeviceProperty
import hep.dataforge.control.base.ReadOnlyDeviceProperty
import hep.dataforge.control.base.asMetaItem
import hep.dataforge.meta.*
import hep.dataforge.meta.transformations.MetaConverter
import hep.dataforge.values.Null
import hep.dataforge.values.double
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.toDuration
public operator fun ReadOnlyDeviceProperty.getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*> =
value ?: MetaItem.ValueItem(Null)
public operator fun DeviceProperty.setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>) {
this.value = value
}
public fun <T : Any> ReadOnlyDeviceProperty.convert(metaConverter: MetaConverter<T>): ReadOnlyProperty<Any?, T> {
return ReadOnlyProperty { thisRef, property ->
getValue(thisRef, property).let { metaConverter.itemToObject(it) }
}
}
public fun <T : Any> DeviceProperty.convert(metaConverter: MetaConverter<T>): ReadWriteProperty<Any?, T> {
return object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return this@convert.getValue(thisRef, property).let { metaConverter.itemToObject(it) }
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this@convert.setValue(thisRef, property, value.let { metaConverter.objectToMetaItem(it) })
}
}
}
public fun ReadOnlyDeviceProperty.double(): ReadOnlyProperty<Any?, Double> = convert(MetaConverter.double)
public fun DeviceProperty.double(): ReadWriteProperty<Any?, Double> = convert(MetaConverter.double)
public fun ReadOnlyDeviceProperty.int(): ReadOnlyProperty<Any?, Int> = convert(MetaConverter.int)
public fun DeviceProperty.int(): ReadWriteProperty<Any?, Int> = convert(MetaConverter.int)
public fun ReadOnlyDeviceProperty.string(): ReadOnlyProperty<Any?, String> = convert(MetaConverter.string)
public fun DeviceProperty.string(): ReadWriteProperty<Any?, String> = convert(MetaConverter.string)
public fun ReadOnlyDeviceProperty.boolean(): ReadOnlyProperty<Any?, Boolean> = convert(MetaConverter.boolean)
public fun DeviceProperty.boolean(): ReadWriteProperty<Any?, Boolean> = convert(MetaConverter.boolean)
//TODO to be moved to DF
private object DurationConverter : MetaConverter<Duration> {
override fun itemToObject(item: MetaItem<*>): Duration = when (item) {
is MetaItem.NodeItem -> {
val unit: DurationUnit = item.node["unit"].enum<DurationUnit>() ?: DurationUnit.SECONDS
val value = item.node[Meta.VALUE_KEY].double ?: error("No value present for Duration")
value.toDuration(unit)
}
is MetaItem.ValueItem -> item.value.double.toDuration(DurationUnit.SECONDS)
}
override fun objectToMetaItem(obj: Duration): MetaItem<*> = obj.toDouble(DurationUnit.SECONDS).asMetaItem()
}
public val MetaConverter.Companion.duration: MetaConverter<Duration> get() = DurationConverter
public fun ReadOnlyDeviceProperty.duration(): ReadOnlyProperty<Any?, Duration> = convert(DurationConverter)
public fun DeviceProperty.duration(): ReadWriteProperty<Any?, Duration> = convert(DurationConverter)

View File

@ -0,0 +1,87 @@
package hep.dataforge.control.controllers
import hep.dataforge.control.base.*
import hep.dataforge.meta.*
import hep.dataforge.meta.transformations.MetaConverter
import kotlinx.coroutines.runBlocking
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.time.Duration
/**
* Blocking read of the value
*/
public operator fun ReadOnlyDeviceProperty.getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*> =
runBlocking(scope.coroutineContext) {
read()
}
public operator fun <T: Any> TypedReadOnlyDeviceProperty<T>.getValue(thisRef: Any?, property: KProperty<*>): T =
runBlocking(scope.coroutineContext) {
readTyped()
}
public operator fun DeviceProperty.setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>) {
this.value = value
}
public operator fun <T: Any> TypedDeviceProperty<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.typedValue = value
}
public fun <T : Any> ReadOnlyDeviceProperty.convert(
metaConverter: MetaConverter<T>,
forceRead: Boolean,
): ReadOnlyProperty<Any?, T> {
return ReadOnlyProperty { thisRef, property ->
runBlocking(scope.coroutineContext) {
read(forceRead).let { metaConverter.itemToObject(it) }
}
}
}
public fun <T : Any> DeviceProperty.convert(
metaConverter: MetaConverter<T>,
forceRead: Boolean,
): ReadWriteProperty<Any?, T> {
return object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T = runBlocking(scope.coroutineContext) {
read(forceRead).let { metaConverter.itemToObject(it) }
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this@convert.setValue(thisRef, property, value.let { metaConverter.objectToMetaItem(it) })
}
}
}
public fun ReadOnlyDeviceProperty.double(forceRead: Boolean = false): ReadOnlyProperty<Any?, Double> =
convert(MetaConverter.double, forceRead)
public fun DeviceProperty.double(forceRead: Boolean = false): ReadWriteProperty<Any?, Double> =
convert(MetaConverter.double, forceRead)
public fun ReadOnlyDeviceProperty.int(forceRead: Boolean = false): ReadOnlyProperty<Any?, Int> =
convert(MetaConverter.int, forceRead)
public fun DeviceProperty.int(forceRead: Boolean = false): ReadWriteProperty<Any?, Int> =
convert(MetaConverter.int, forceRead)
public fun ReadOnlyDeviceProperty.string(forceRead: Boolean = false): ReadOnlyProperty<Any?, String> =
convert(MetaConverter.string, forceRead)
public fun DeviceProperty.string(forceRead: Boolean = false): ReadWriteProperty<Any?, String> =
convert(MetaConverter.string, forceRead)
public fun ReadOnlyDeviceProperty.boolean(forceRead: Boolean = false): ReadOnlyProperty<Any?, Boolean> =
convert(MetaConverter.boolean, forceRead)
public fun DeviceProperty.boolean(forceRead: Boolean = false): ReadWriteProperty<Any?, Boolean> =
convert(MetaConverter.boolean, forceRead)
public fun ReadOnlyDeviceProperty.duration(forceRead: Boolean = false): ReadOnlyProperty<Any?, Duration> =
convert(DurationConverter, forceRead)
public fun DeviceProperty.duration(forceRead: Boolean = false): ReadWriteProperty<Any?, Duration> =
convert(DurationConverter, forceRead)

View File

@ -19,7 +19,7 @@ repositories{
dependencies{ dependencies{
implementation(project(":dataforge-device-core")) implementation(project(":dataforge-device-core"))
implementation(project(":dataforge-device-server")) implementation(project(":dataforge-device-server"))
implementation(project(":dataforge-device-client")) implementation(project(":dataforge-magix-client"))
implementation("no.tornado:tornadofx:1.7.20") implementation("no.tornado:tornadofx:1.7.20")
implementation(kotlin("stdlib-jdk8")) implementation(kotlin("stdlib-jdk8"))
implementation("kscience.plotlykt:plotlykt-server:0.3.0-dev-2") implementation("kscience.plotlykt:plotlykt-server:0.3.0-dev-2")

View File

@ -1,3 +1,5 @@
import ru.mipt.npm.gradle.useFx
plugins { plugins {
id("ru.mipt.npm.jvm") id("ru.mipt.npm.jvm")
id("ru.mipt.npm.publish") id("ru.mipt.npm.publish")
@ -7,6 +9,7 @@ plugins {
kotlin{ kotlin{
explicitApi = null explicitApi = null
useFx(ru.mipt.npm.gradle.FXModule.CONTROLS)
} }
val ktorVersion: String by rootProject.extra val ktorVersion: String by rootProject.extra
@ -14,4 +17,5 @@ val ktorVersion: String by rootProject.extra
dependencies { dependencies {
implementation(project(":dataforge-device-core")) implementation(project(":dataforge-device-core"))
implementation(project(":dataforge-magix-client")) implementation(project(":dataforge-magix-client"))
implementation("no.tornado:tornadofx:1.7.20")
} }

View File

@ -0,0 +1,54 @@
package ru.mipt.npm.devices.pimotionmaster
import hep.dataforge.context.Global
import hep.dataforge.control.controllers.DeviceManager
import hep.dataforge.control.controllers.installing
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.scene.Parent
import tornadofx.*
class PiMotionMasterApp : App(PiMotionMasterView::class)
class PiMotionMasterController : Controller() {
//initialize context
val context = Global.context("piMotionMaster")
//initialize deviceManager plugin
val deviceManager: DeviceManager = context.plugins.load(DeviceManager)
// install device
val motionMaster: PiMotionMasterDevice by deviceManager.installing(PiMotionMasterDevice)
}
class PiMotionMasterView : View() {
private val controller: PiMotionMasterController by inject()
override val root: Parent = borderpane {
top {
hbox {
val host = SimpleStringProperty("127.0.0.1")
val port = SimpleIntegerProperty(10024)
fieldset("Address:") {
field("Host:") {
textfield(host)
}
field("Port:") {
textfield(port)
}
}
button("Connect") {
action {
controller.motionMaster.connect(host.get(), port.get())
}
}
}
}
}
}
fun main() {
launch<PiMotionMasterApp>()
}

View File

@ -1,18 +1,14 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package ru.mipt.npm.devices.pimotionmaster package ru.mipt.npm.devices.pimotionmaster
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.control.api.DeviceHub import hep.dataforge.control.api.DeviceHub
import hep.dataforge.control.api.PropertyDescriptor import hep.dataforge.control.api.PropertyDescriptor
import hep.dataforge.control.base.* import hep.dataforge.control.base.*
import hep.dataforge.control.controllers.boolean import hep.dataforge.control.controllers.*
import hep.dataforge.control.controllers.double import hep.dataforge.control.ports.*
import hep.dataforge.control.controllers.duration import hep.dataforge.meta.*
import hep.dataforge.control.ports.Port
import hep.dataforge.control.ports.PortProxy
import hep.dataforge.control.ports.send
import hep.dataforge.control.ports.withDelimiter
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.asMetaItem
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.values.Null import hep.dataforge.values.Null
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
@ -22,30 +18,80 @@ import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import tornadofx.*
import java.util.*
import kotlin.error
import kotlin.time.Duration import kotlin.time.Duration
class PiMotionMasterDevice(
public class PiMotionMasterDevice(
context: Context, context: Context,
axes: List<String>, private val portFactory: PortFactory = TcpPort,
private val portFactory: suspend (MetaItem<*>?) -> Port,
) : DeviceBase(context), DeviceHub { ) : DeviceBase(context), DeviceHub {
override val scope: CoroutineScope = CoroutineScope( override val scope: CoroutineScope = CoroutineScope(
context.coroutineContext + SupervisorJob(context.coroutineContext[Job]) context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
) )
public val port: DeviceProperty by writingVirtual(Null) { val address: DeviceProperty by writingVirtual(Null) {
info = "The port for TCP connector" info = "The port for TCP connector"
} }
public val timeout: DeviceProperty by writingVirtual(Null) {
val timeout: DeviceProperty by writingVirtual(200.asValue()) {
info = "Timeout" info = "Timeout"
} }
public var timeoutValue: Duration by timeout.duration() var timeoutValue: Duration by timeout.duration()
private val connector = PortProxy { portFactory(port.value) } private val connector = PortProxy { portFactory(address.value.node ?: Meta.EMPTY, context) }
/**
* Name-friendly accessor for axis
*/
var axes: Map<String, Axis> = emptyMap()
private set
override val devices: Map<NameToken, Axis> = axes.mapKeys { (key, _) -> NameToken(key) }
private suspend fun failIfError(message: (Int) -> String = { "Failed with error code $it" }) {
val errorCode = getErrorCode()
if (errorCode != 0) error(message(errorCode))
}
val connect: DeviceAction by acting({
info = "Connect to specific port and initialize axis"
}) { portSpec ->
//Clear current actions if present
if (address.value != null) {
stop()
}
//Update port
address.value = portSpec
//Initialize axes
if (portSpec != null) {
val idn = identity.read()
failIfError { "Can't connect to $portSpec. Error code: $it" }
logger.info { "Connected to $idn on $portSpec" }
val ids = request("SAI?")
if (ids != axes.keys.toList()) {
//re-define axes if needed
axes = ids.associateWith { Axis(it) }
}
ids.map { it.asValue() }.asValue().asMetaItem()
initialize()
failIfError()
}
}
fun connect(host: String, port: Int) {
scope.launch {
connect(Meta {
"host" put host
"port" put port
})
}
}
private val mutex = Mutex() private val mutex = Mutex()
@ -64,7 +110,7 @@ public class PiMotionMasterDevice(
connector.send(stringToSend) connector.send(stringToSend)
} }
public suspend fun getErrorCode(): Int = mutex.withLock { suspend fun getErrorCode(): Int = mutex.withLock {
withTimeout(timeoutValue) { withTimeout(timeoutValue) {
sendCommandInternal("ERR?") sendCommandInternal("ERR?")
val errorString = connector.receiving().withDelimiter("\n").first() val errorString = connector.receiving().withDelimiter("\n").first()
@ -72,7 +118,6 @@ public class PiMotionMasterDevice(
} }
} }
/** /**
* Send a synchronous request and receive a list of lines as a response * Send a synchronous request and receive a list of lines as a response
*/ */
@ -110,23 +155,26 @@ public class PiMotionMasterDevice(
} }
} }
val initialize: DeviceAction by acting {
public val initialize: DeviceAction by acting {
send("INI") send("INI")
} }
public val firmwareVersion: ReadOnlyDeviceProperty by readingString { val identity: ReadOnlyDeviceProperty by readingString {
request("*IDN?").first()
}
val firmwareVersion: ReadOnlyDeviceProperty by readingString {
request("VER?").first() request("VER?").first()
} }
public val stop: DeviceAction by acting( val stop: DeviceAction by acting(
descriptorBuilder = { descriptorBuilder = {
info = "Stop all axis" info = "Stop all axis"
}, },
action = { send("STP") } action = { send("STP") }
) )
public inner class Axis(public val axisId: String) : DeviceBase(context) { inner class Axis(val axisId: String) : DeviceBase(context) {
override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope
private suspend fun readAxisBoolean(command: String): Boolean = private suspend fun readAxisBoolean(command: String): Boolean =
@ -140,45 +188,49 @@ public class PiMotionMasterDevice(
"0" "0"
} }
send(command, axisId, boolean) send(command, axisId, boolean)
failIfError()
return value return value
} }
private fun axisBooleanProperty(command: String, descriptorBuilder: PropertyDescriptor.() -> Unit = {}) = private fun axisBooleanProperty(command: String, descriptorBuilder: PropertyDescriptor.() -> Unit = {}) =
writingBoolean<Axis>( writingBoolean(
getter = { readAxisBoolean("$command?") }, getter = { readAxisBoolean("$command?") },
setter = { _, newValue -> writeAxisBoolean(command, newValue) }, setter = { _, newValue ->
writeAxisBoolean(command, newValue)
},
descriptorBuilder = descriptorBuilder descriptorBuilder = descriptorBuilder
) )
private fun axisNumberProperty(command: String, descriptorBuilder: PropertyDescriptor.() -> Unit = {}) = private fun axisNumberProperty(command: String, descriptorBuilder: PropertyDescriptor.() -> Unit = {}) =
writingDouble<Axis>( writingDouble(
getter = { getter = {
requestAndParse("$command?", axisId)[axisId]?.toDoubleOrNull() requestAndParse("$command?", axisId)[axisId]?.toDoubleOrNull()
?: error("Malformed $command response. Should include float value for $axisId") ?: error("Malformed $command response. Should include float value for $axisId")
}, },
setter = { _, newValue -> setter = { _, newValue ->
send(command, axisId, newValue.toString()) send(command, axisId, newValue.toString())
failIfError()
newValue newValue
}, },
descriptorBuilder = descriptorBuilder descriptorBuilder = descriptorBuilder
) )
public val enabled: DeviceProperty by axisBooleanProperty("EAX") { val enabled by axisBooleanProperty("EAX") {
info = "Motor enable state." info = "Motor enable state."
} }
public val halt: DeviceAction by acting { val halt: DeviceAction by acting {
send("HLT", axisId) send("HLT", axisId)
} }
public val targetPosition: DeviceProperty by axisNumberProperty("MOV") { val targetPosition by axisNumberProperty("MOV") {
info = """ info = """
Sets a new absolute target position for the specified axis. Sets a new absolute target position for the specified axis.
Servo mode must be switched on for the commanded axis prior to using this command (closed-loop operation). Servo mode must be switched on for the commanded axis prior to using this command (closed-loop operation).
""".trimIndent() """.trimIndent()
} }
public val onTarget: ReadOnlyDeviceProperty by readingBoolean( val onTarget: TypedReadOnlyDeviceProperty<Boolean> by readingBoolean(
descriptorBuilder = { descriptorBuilder = {
info = "Queries the on-target state of the specified axis." info = "Queries the on-target state of the specified axis."
}, },
@ -187,7 +239,7 @@ public class PiMotionMasterDevice(
} }
) )
public val reference: ReadOnlyDeviceProperty by readingBoolean( val reference: ReadOnlyDeviceProperty by readingBoolean(
descriptorBuilder = { descriptorBuilder = {
info = "Get Referencing Result" info = "Get Referencing Result"
}, },
@ -200,36 +252,40 @@ public class PiMotionMasterDevice(
send("FRF", axisId) send("FRF", axisId)
} }
public val position: DeviceProperty by axisNumberProperty("POS") { val position: TypedDeviceProperty<Double> by axisNumberProperty("POS") {
info = "The current axis position." info = "The current axis position."
} }
var positionValue by position.double() val openLoopTarget: DeviceProperty by axisNumberProperty("OMA") {
public val openLoopTarget: DeviceProperty by axisNumberProperty("OMA") {
info = "Position for open-loop operation." info = "Position for open-loop operation."
} }
public val closedLoop: DeviceProperty by axisBooleanProperty("SVO") { val closedLoop: TypedDeviceProperty<Boolean> by axisBooleanProperty("SVO") {
info = "Servo closed loop mode" info = "Servo closed loop mode"
} }
var closedLoopValue by closedLoop.boolean() val velocity: TypedDeviceProperty<Double> by axisNumberProperty("VEL") {
public val velocity: DeviceProperty by axisNumberProperty("VEL") {
info = "Velocity value for closed-loop operation" info = "Velocity value for closed-loop operation"
} }
val move by acting {
val target = it.double ?: it.node["target"].double ?: error("Unacceptable target value $it")
closedLoop.write(true)
//optionally set velocity
it.node["velocity"].double?.let { v ->
velocity.write(v)
}
position.write(target)
//read `onTarget` and `position` properties in a cycle until movement is complete
while (!onTarget.readTyped(true)) {
position.read(true)
delay(200)
}
}
} }
val axisIds: ReadOnlyDeviceProperty by reading { companion object : DeviceFactory<PiMotionMasterDevice> {
request("SAI?").map { it.asValue() }.asValue().asMetaItem() override fun invoke(meta: Meta, context: Context): PiMotionMasterDevice = PiMotionMasterDevice(context)
} }
override val devices: Map<NameToken, Axis> = axes.associate { NameToken(it) to Axis(it) }
/**
* Name-friendly accessor for axis
*/
val axes: Map<String, Axis> get() = devices.mapKeys { it.toString() }
} }