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

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

View File

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

View File

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

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{
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")

View File

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

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