Refactor delegated properties
This commit is contained in:
parent
4b9f535002
commit
eb3121aed4
@ -1,10 +1,14 @@
|
||||
package hep.dataforge.control.base
|
||||
|
||||
import hep.dataforge.control.api.ActionDescriptor
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.asMetaItem
|
||||
|
||||
public interface DeviceAction {
|
||||
public val name: String
|
||||
public val descriptor: ActionDescriptor
|
||||
public suspend operator fun invoke(arg: MetaItem<*>? = null): MetaItem<*>?
|
||||
}
|
||||
}
|
||||
|
||||
public suspend operator fun DeviceAction.invoke(meta: Meta): MetaItem<*>? = invoke(meta.asMetaItem())
|
@ -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))
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package hep.dataforge.control.base
|
||||
|
||||
import hep.dataforge.control.api.PropertyDescriptor
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.transformations.MetaConverter
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.asValue
|
||||
@ -9,13 +10,22 @@ import kotlin.properties.PropertyDelegateProvider
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
private fun <D : DeviceBase> D.provideProperty(): ReadOnlyProperty<D, ReadOnlyDeviceProperty> =
|
||||
ReadOnlyProperty { _: D, property: KProperty<*> ->
|
||||
val name = property.name
|
||||
return@ReadOnlyProperty properties[name]!!
|
||||
private fun <D : DeviceBase> D.provideProperty(name: String): ReadOnlyProperty<D, ReadOnlyDeviceProperty> =
|
||||
ReadOnlyProperty { _: D, _: KProperty<*> ->
|
||||
return@ReadOnlyProperty properties.getValue(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 TypedReadOnlyPropertyDelegate<T> = ReadOnlyProperty<DeviceBase, TypedReadOnlyDeviceProperty<T>>
|
||||
|
||||
private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
|
||||
val owner: D,
|
||||
@ -27,7 +37,22 @@ private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
|
||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate {
|
||||
val name = property.name
|
||||
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,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> Number,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Number>> = TypedReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
MetaConverter.number,
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
val number = getter()
|
||||
@ -71,9 +97,10 @@ public fun DeviceBase.readingString(
|
||||
default: String? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> String,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<String>> = TypedReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
MetaConverter.string,
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
val number = getter()
|
||||
@ -85,9 +112,10 @@ public fun DeviceBase.readingBoolean(
|
||||
default: Boolean? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> Boolean,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Boolean>> = TypedReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
MetaConverter.boolean,
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
val boolean = getter()
|
||||
@ -99,22 +127,31 @@ public fun DeviceBase.readingMeta(
|
||||
default: Meta? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend MetaBuilder.() -> Unit,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Meta>> = TypedReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default?.let { MetaItem.NodeItem(it) },
|
||||
MetaConverter.meta,
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
MetaItem.NodeItem(MetaBuilder().apply { getter() })
|
||||
}
|
||||
)
|
||||
|
||||
private fun DeviceBase.provideMutableProperty(): ReadOnlyProperty<DeviceBase, DeviceProperty> =
|
||||
ReadOnlyProperty { _: DeviceBase, property: KProperty<*> ->
|
||||
val name = property.name
|
||||
private fun DeviceBase.provideMutableProperty(name: String): ReadOnlyProperty<DeviceBase, DeviceProperty> =
|
||||
ReadOnlyProperty { _: DeviceBase, _: KProperty<*> ->
|
||||
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 TypedPropertyDelegate<T> = ReadOnlyProperty<DeviceBase, TypedDeviceProperty<T>>
|
||||
|
||||
private class DevicePropertyProvider<D : DeviceBase>(
|
||||
val owner: D,
|
||||
@ -127,7 +164,23 @@ private class DevicePropertyProvider<D : DeviceBase>(
|
||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate {
|
||||
val name = property.name
|
||||
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 }
|
||||
)
|
||||
|
||||
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(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (Double) -> Double,
|
||||
setter: suspend (oldValue: Double?, newValue: Double) -> Double?,
|
||||
): PropertyDelegateProvider<D, PropertyDelegate> {
|
||||
): PropertyDelegateProvider<D, TypedPropertyDelegate<Double>> {
|
||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||
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()
|
||||
}
|
||||
|
||||
return DevicePropertyProvider(
|
||||
return TypedDevicePropertyProvider(
|
||||
this,
|
||||
MetaItem.ValueItem(Double.NaN.asValue()),
|
||||
MetaConverter.double,
|
||||
descriptorBuilder,
|
||||
innerGetter,
|
||||
innerSetter
|
||||
@ -190,7 +254,7 @@ public fun <D : DeviceBase> D.writingBoolean(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (Boolean?) -> Boolean,
|
||||
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?,
|
||||
): PropertyDelegateProvider<D, PropertyDelegate> {
|
||||
): PropertyDelegateProvider<D, TypedPropertyDelegate<Boolean>> {
|
||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||
MetaItem.ValueItem(getter(it.boolean).asValue())
|
||||
}
|
||||
@ -200,9 +264,10 @@ public fun <D : DeviceBase> D.writingBoolean(
|
||||
?.asMetaItem()
|
||||
}
|
||||
|
||||
return DevicePropertyProvider(
|
||||
return TypedDevicePropertyProvider(
|
||||
this,
|
||||
MetaItem.ValueItem(Null),
|
||||
MetaConverter.boolean,
|
||||
descriptorBuilder,
|
||||
innerGetter,
|
||||
innerSetter
|
||||
|
@ -1,6 +1,27 @@
|
||||
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.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
|
@ -1,14 +1,13 @@
|
||||
package hep.dataforge.control.controllers
|
||||
|
||||
import hep.dataforge.context.AbstractPlugin
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.PluginFactory
|
||||
import hep.dataforge.context.PluginTag
|
||||
import hep.dataforge.context.*
|
||||
import hep.dataforge.control.api.Device
|
||||
import hep.dataforge.control.api.DeviceHub
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
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 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))
|
||||
}
|
||||
|
||||
|
@ -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)
|
@ -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)
|
@ -19,7 +19,7 @@ repositories{
|
||||
dependencies{
|
||||
implementation(project(":dataforge-device-core"))
|
||||
implementation(project(":dataforge-device-server"))
|
||||
implementation(project(":dataforge-device-client"))
|
||||
implementation(project(":dataforge-magix-client"))
|
||||
implementation("no.tornado:tornadofx:1.7.20")
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
implementation("kscience.plotlykt:plotlykt-server:0.3.0-dev-2")
|
||||
|
@ -1,3 +1,5 @@
|
||||
import ru.mipt.npm.gradle.useFx
|
||||
|
||||
plugins {
|
||||
id("ru.mipt.npm.jvm")
|
||||
id("ru.mipt.npm.publish")
|
||||
@ -7,6 +9,7 @@ plugins {
|
||||
|
||||
kotlin{
|
||||
explicitApi = null
|
||||
useFx(ru.mipt.npm.gradle.FXModule.CONTROLS)
|
||||
}
|
||||
|
||||
val ktorVersion: String by rootProject.extra
|
||||
@ -14,4 +17,5 @@ val ktorVersion: String by rootProject.extra
|
||||
dependencies {
|
||||
implementation(project(":dataforge-device-core"))
|
||||
implementation(project(":dataforge-magix-client"))
|
||||
implementation("no.tornado:tornadofx:1.7.20")
|
||||
}
|
||||
|
@ -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>()
|
||||
}
|
@ -1,18 +1,14 @@
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package ru.mipt.npm.devices.pimotionmaster
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.control.api.DeviceHub
|
||||
import hep.dataforge.control.api.PropertyDescriptor
|
||||
import hep.dataforge.control.base.*
|
||||
import hep.dataforge.control.controllers.boolean
|
||||
import hep.dataforge.control.controllers.double
|
||||
import hep.dataforge.control.controllers.duration
|
||||
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.control.controllers.*
|
||||
import hep.dataforge.control.ports.*
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.asValue
|
||||
@ -22,30 +18,80 @@ import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import tornadofx.*
|
||||
import java.util.*
|
||||
import kotlin.error
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
||||
public class PiMotionMasterDevice(
|
||||
class PiMotionMasterDevice(
|
||||
context: Context,
|
||||
axes: List<String>,
|
||||
private val portFactory: suspend (MetaItem<*>?) -> Port,
|
||||
private val portFactory: PortFactory = TcpPort,
|
||||
) : DeviceBase(context), DeviceHub {
|
||||
|
||||
override val scope: CoroutineScope = CoroutineScope(
|
||||
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"
|
||||
}
|
||||
|
||||
public val timeout: DeviceProperty by writingVirtual(Null) {
|
||||
|
||||
val timeout: DeviceProperty by writingVirtual(200.asValue()) {
|
||||
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()
|
||||
|
||||
@ -64,7 +110,7 @@ public class PiMotionMasterDevice(
|
||||
connector.send(stringToSend)
|
||||
}
|
||||
|
||||
public suspend fun getErrorCode(): Int = mutex.withLock {
|
||||
suspend fun getErrorCode(): Int = mutex.withLock {
|
||||
withTimeout(timeoutValue) {
|
||||
sendCommandInternal("ERR?")
|
||||
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
|
||||
*/
|
||||
@ -110,23 +155,26 @@ public class PiMotionMasterDevice(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public val initialize: DeviceAction by acting {
|
||||
val initialize: DeviceAction by acting {
|
||||
send("INI")
|
||||
}
|
||||
|
||||
public val firmwareVersion: ReadOnlyDeviceProperty by readingString {
|
||||
val identity: ReadOnlyDeviceProperty by readingString {
|
||||
request("*IDN?").first()
|
||||
}
|
||||
|
||||
val firmwareVersion: ReadOnlyDeviceProperty by readingString {
|
||||
request("VER?").first()
|
||||
}
|
||||
|
||||
public val stop: DeviceAction by acting(
|
||||
val stop: DeviceAction by acting(
|
||||
descriptorBuilder = {
|
||||
info = "Stop all axis"
|
||||
},
|
||||
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
|
||||
|
||||
private suspend fun readAxisBoolean(command: String): Boolean =
|
||||
@ -140,45 +188,49 @@ public class PiMotionMasterDevice(
|
||||
"0"
|
||||
}
|
||||
send(command, axisId, boolean)
|
||||
failIfError()
|
||||
return value
|
||||
}
|
||||
|
||||
private fun axisBooleanProperty(command: String, descriptorBuilder: PropertyDescriptor.() -> Unit = {}) =
|
||||
writingBoolean<Axis>(
|
||||
writingBoolean(
|
||||
getter = { readAxisBoolean("$command?") },
|
||||
setter = { _, newValue -> writeAxisBoolean(command, newValue) },
|
||||
setter = { _, newValue ->
|
||||
writeAxisBoolean(command, newValue)
|
||||
},
|
||||
descriptorBuilder = descriptorBuilder
|
||||
)
|
||||
|
||||
private fun axisNumberProperty(command: String, descriptorBuilder: PropertyDescriptor.() -> Unit = {}) =
|
||||
writingDouble<Axis>(
|
||||
writingDouble(
|
||||
getter = {
|
||||
requestAndParse("$command?", axisId)[axisId]?.toDoubleOrNull()
|
||||
?: error("Malformed $command response. Should include float value for $axisId")
|
||||
},
|
||||
setter = { _, newValue ->
|
||||
send(command, axisId, newValue.toString())
|
||||
failIfError()
|
||||
newValue
|
||||
},
|
||||
descriptorBuilder = descriptorBuilder
|
||||
)
|
||||
|
||||
public val enabled: DeviceProperty by axisBooleanProperty("EAX") {
|
||||
val enabled by axisBooleanProperty("EAX") {
|
||||
info = "Motor enable state."
|
||||
}
|
||||
|
||||
public val halt: DeviceAction by acting {
|
||||
val halt: DeviceAction by acting {
|
||||
send("HLT", axisId)
|
||||
}
|
||||
|
||||
public val targetPosition: DeviceProperty by axisNumberProperty("MOV") {
|
||||
val targetPosition by axisNumberProperty("MOV") {
|
||||
info = """
|
||||
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).
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
public val onTarget: ReadOnlyDeviceProperty by readingBoolean(
|
||||
val onTarget: TypedReadOnlyDeviceProperty<Boolean> by readingBoolean(
|
||||
descriptorBuilder = {
|
||||
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 = {
|
||||
info = "Get Referencing Result"
|
||||
},
|
||||
@ -200,36 +252,40 @@ public class PiMotionMasterDevice(
|
||||
send("FRF", axisId)
|
||||
}
|
||||
|
||||
public val position: DeviceProperty by axisNumberProperty("POS") {
|
||||
val position: TypedDeviceProperty<Double> by axisNumberProperty("POS") {
|
||||
info = "The current axis position."
|
||||
}
|
||||
|
||||
var positionValue by position.double()
|
||||
|
||||
public val openLoopTarget: DeviceProperty by axisNumberProperty("OMA") {
|
||||
val openLoopTarget: DeviceProperty by axisNumberProperty("OMA") {
|
||||
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"
|
||||
}
|
||||
|
||||
var closedLoopValue by closedLoop.boolean()
|
||||
|
||||
public val velocity: DeviceProperty by axisNumberProperty("VEL") {
|
||||
val velocity: TypedDeviceProperty<Double> by axisNumberProperty("VEL") {
|
||||
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 {
|
||||
request("SAI?").map { it.asValue() }.asValue().asMetaItem()
|
||||
companion object : DeviceFactory<PiMotionMasterDevice> {
|
||||
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() }
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user