Unisolate properties
This commit is contained in:
parent
10bb85ac83
commit
4b5bc40a4f
@ -1,3 +1,8 @@
|
||||
plugins{
|
||||
kotlin("jvm") version "1.4.0" apply false
|
||||
kotlin("js") version "1.4.0" apply false
|
||||
}
|
||||
|
||||
val dataforgeVersion by extra("0.1.9-dev-2")
|
||||
|
||||
allprojects {
|
||||
@ -6,6 +11,7 @@ allprojects {
|
||||
maven("https://dl.bintray.com/pdvrieze/maven")
|
||||
maven("http://maven.jzy3d.org/releases")
|
||||
maven("https://kotlin.bintray.com/js-externals")
|
||||
maven("https://maven.pkg.github.com/altavir/kotlin-logging/")
|
||||
}
|
||||
|
||||
group = "hep.dataforge"
|
||||
|
@ -1,19 +1,11 @@
|
||||
plugins {
|
||||
id("kscience.mpp")
|
||||
id("kscience.publish")
|
||||
id("ru.mipt.npm.mpp")
|
||||
id("ru.mipt.npm.publish")
|
||||
}
|
||||
|
||||
val ktorVersion: String by extra("1.4.0")
|
||||
|
||||
kotlin {
|
||||
// js {
|
||||
// browser {
|
||||
// dceTask {
|
||||
// keep("ktor-ktor-io.\$\$importsForInline\$\$.ktor-ktor-io.io.ktor.utils.io")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
|
@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id("kscience.mpp")
|
||||
id("kscience.publish")
|
||||
id("ru.mipt.npm.mpp")
|
||||
id("ru.mipt.npm.publish")
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
@ -15,7 +15,6 @@ kotlin {
|
||||
commonMain{
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-io:$dataforgeVersion")
|
||||
//implementation("org.jetbrains.kotlinx:atomicfu-common:0.14.3")
|
||||
}
|
||||
}
|
||||
jvmMain{
|
||||
|
@ -3,7 +3,6 @@ package hep.dataforge.control.api
|
||||
import hep.dataforge.control.api.Device.Companion.DEVICE_TARGET
|
||||
import hep.dataforge.io.Envelope
|
||||
import hep.dataforge.io.EnvelopeBuilder
|
||||
import hep.dataforge.io.Responder
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.provider.Type
|
||||
@ -15,7 +14,7 @@ import kotlinx.io.Closeable
|
||||
* General interface describing a managed Device
|
||||
*/
|
||||
@Type(DEVICE_TARGET)
|
||||
public interface Device : Responder, Closeable {
|
||||
public interface Device : Closeable {
|
||||
/**
|
||||
* List of supported property descriptors
|
||||
*/
|
||||
@ -73,7 +72,7 @@ public interface Device : Responder, Closeable {
|
||||
* [setProperty], [getProperty] or [execute] and not defined for a generic device.
|
||||
*
|
||||
*/
|
||||
override suspend fun respond(request: Envelope): EnvelopeBuilder = error("No binary response defined")
|
||||
public suspend fun respondWithData(request: Envelope): EnvelopeBuilder = error("No binary response defined")
|
||||
|
||||
override fun close() {
|
||||
scope.cancel("The device is closed")
|
||||
|
@ -6,9 +6,9 @@ import hep.dataforge.meta.MetaItem
|
||||
* PropertyChangeListener Interface
|
||||
* [value] is a new value that property has after a change; null is for invalid state.
|
||||
*/
|
||||
interface DeviceListener {
|
||||
fun propertyChanged(propertyName: String, value: MetaItem<*>?)
|
||||
fun actionExecuted(action: String, argument: MetaItem<*>?, result: MetaItem<*>?) {}
|
||||
public interface DeviceListener {
|
||||
public fun propertyChanged(propertyName: String, value: MetaItem<*>?)
|
||||
public fun actionExecuted(action: String, argument: MetaItem<*>?, result: MetaItem<*>?) {}
|
||||
|
||||
//TODO add general message listener method
|
||||
}
|
@ -6,17 +6,17 @@ import hep.dataforge.meta.string
|
||||
/**
|
||||
* A descriptor for property
|
||||
*/
|
||||
class PropertyDescriptor(name: String) : Scheme() {
|
||||
val name by string(name)
|
||||
var info by string()
|
||||
public class PropertyDescriptor(name: String) : Scheme() {
|
||||
public val name: String by string(name)
|
||||
public var info: String? by string()
|
||||
}
|
||||
|
||||
/**
|
||||
* A descriptor for property
|
||||
*/
|
||||
class ActionDescriptor(name: String) : Scheme() {
|
||||
val name by string(name)
|
||||
var info by string()
|
||||
public class ActionDescriptor(name: String) : Scheme() {
|
||||
public val name: String by string(name)
|
||||
public var info: String? by string()
|
||||
//var descriptor by spec(ItemDescriptor)
|
||||
}
|
||||
|
||||
|
@ -1,74 +1,10 @@
|
||||
package hep.dataforge.control.base
|
||||
|
||||
import hep.dataforge.control.api.ActionDescriptor
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.values.Value
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
interface Action {
|
||||
val name: String
|
||||
val descriptor: ActionDescriptor
|
||||
suspend operator fun invoke(arg: MetaItem<*>? = null): MetaItem<*>?
|
||||
}
|
||||
|
||||
private fun DeviceBase.actionExecuted(action: String, argument: MetaItem<*>?, result: MetaItem<*>?){
|
||||
notifyListeners { actionExecuted(action, argument, result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A stand-alone action
|
||||
*/
|
||||
class IsolatedAction(
|
||||
override val name: String,
|
||||
override val descriptor: ActionDescriptor,
|
||||
val callback: (action: String, argument: MetaItem<*>?, result: MetaItem<*>?) -> Unit,
|
||||
val block: suspend (MetaItem<*>?) -> MetaItem<*>?
|
||||
) : Action {
|
||||
override suspend fun invoke(arg: MetaItem<*>?): MetaItem<*>? = block(arg).also {
|
||||
callback(name, arg, it)
|
||||
}
|
||||
}
|
||||
|
||||
class ActionDelegate<D : DeviceBase>(
|
||||
val owner: D,
|
||||
val descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
val block: suspend (MetaItem<*>?) -> MetaItem<*>?
|
||||
) : ReadOnlyProperty<D, Action> {
|
||||
override fun getValue(thisRef: D, property: KProperty<*>): Action {
|
||||
val name = property.name
|
||||
return owner.registerAction(name) {
|
||||
IsolatedAction(name, ActionDescriptor(name).apply(descriptorBuilder), owner::actionExecuted, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <D : DeviceBase> D.request(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend (MetaItem<*>?) -> MetaItem<*>?
|
||||
): ActionDelegate<D> = ActionDelegate(this, descriptorBuilder, block)
|
||||
|
||||
fun <D : DeviceBase> D.requestValue(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend (MetaItem<*>?) -> Any?
|
||||
): ActionDelegate<D> = ActionDelegate(this, descriptorBuilder) {
|
||||
val res = block(it)
|
||||
MetaItem.ValueItem(Value.of(res))
|
||||
}
|
||||
|
||||
fun <D : DeviceBase> D.requestMeta(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend MetaBuilder.(MetaItem<*>?) -> Unit
|
||||
): ActionDelegate<D> = ActionDelegate(this, descriptorBuilder) {
|
||||
val res = MetaBuilder().apply { block(it) }
|
||||
MetaItem.NodeItem(res)
|
||||
}
|
||||
|
||||
fun <D : DeviceBase> D.action(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend (MetaItem<*>?) -> Unit
|
||||
): ActionDelegate<D> = ActionDelegate(this, descriptorBuilder) {
|
||||
block(it)
|
||||
null
|
||||
public interface Action {
|
||||
public val name: String
|
||||
public val descriptor: ActionDescriptor
|
||||
public suspend operator fun invoke(arg: MetaItem<*>? = null): MetaItem<*>?
|
||||
}
|
@ -5,14 +5,23 @@ import hep.dataforge.control.api.Device
|
||||
import hep.dataforge.control.api.DeviceListener
|
||||
import hep.dataforge.control.api.PropertyDescriptor
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Baseline implementation of [Device] interface
|
||||
*/
|
||||
abstract class DeviceBase : Device {
|
||||
private val properties = HashMap<String, ReadOnlyDeviceProperty>()
|
||||
private val actions = HashMap<String, Action>()
|
||||
public abstract class DeviceBase : Device {
|
||||
private val _properties = HashMap<String, ReadOnlyDeviceProperty>()
|
||||
public val properties: Map<String, ReadOnlyDeviceProperty> get() = _properties
|
||||
private val _actions = HashMap<String, Action>()
|
||||
public val actions: Map<String, Action> get() = _actions
|
||||
|
||||
private val listeners = ArrayList<Pair<Any?, DeviceListener>>(4)
|
||||
|
||||
@ -24,11 +33,11 @@ abstract class DeviceBase : Device {
|
||||
listeners.removeAll { it.first == owner }
|
||||
}
|
||||
|
||||
fun notifyListeners(block: DeviceListener.() -> Unit) {
|
||||
internal fun notifyListeners(block: DeviceListener.() -> Unit) {
|
||||
listeners.forEach { it.second.block() }
|
||||
}
|
||||
|
||||
fun notifyPropertyChanged(propertyName: String) {
|
||||
public fun notifyPropertyChanged(propertyName: String) {
|
||||
scope.launch {
|
||||
val value = getProperty(propertyName)
|
||||
notifyListeners { propertyChanged(propertyName, value) }
|
||||
@ -36,41 +45,188 @@ abstract class DeviceBase : Device {
|
||||
}
|
||||
|
||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
||||
get() = properties.values.map { it.descriptor }
|
||||
get() = _properties.values.map { it.descriptor }
|
||||
|
||||
override val actionDescriptors: Collection<ActionDescriptor>
|
||||
get() = actions.values.map { it.descriptor }
|
||||
get() = _actions.values.map { it.descriptor }
|
||||
|
||||
internal fun registerProperty(name: String, builder: () -> ReadOnlyDeviceProperty): ReadOnlyDeviceProperty {
|
||||
return properties.getOrPut(name, builder)
|
||||
internal fun <P : ReadOnlyDeviceProperty> registerProperty(name: String, property: P) {
|
||||
if (_properties.contains(name)) error("Property with name $name already registered")
|
||||
_properties[name] = property
|
||||
}
|
||||
|
||||
internal fun registerMutableProperty(name: String, builder: () -> DeviceProperty): DeviceProperty {
|
||||
return properties.getOrPut(name, builder) as DeviceProperty
|
||||
}
|
||||
|
||||
internal fun registerAction(name: String, builder: () -> Action): Action {
|
||||
return actions.getOrPut(name, builder)
|
||||
internal fun registerAction(name: String, action: Action) {
|
||||
if (_actions.contains(name)) error("Action with name $name already registered")
|
||||
_actions[name] = action
|
||||
}
|
||||
|
||||
override suspend fun getProperty(propertyName: String): MetaItem<*> =
|
||||
(properties[propertyName] ?: error("Property with name $propertyName not defined")).read()
|
||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).read()
|
||||
|
||||
override suspend fun invalidateProperty(propertyName: String) {
|
||||
(properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate()
|
||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate()
|
||||
}
|
||||
|
||||
override suspend fun setProperty(propertyName: String, value: MetaItem<*>) {
|
||||
(properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write(
|
||||
(_properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write(
|
||||
value
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun execute(command: String, argument: MetaItem<*>?): MetaItem<*>? =
|
||||
(actions[command] ?: error("Request with name $command not defined")).invoke(argument)
|
||||
(_actions[command] ?: error("Request with name $command not defined")).invoke(argument)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private open inner class BasicReadOnlyDeviceProperty(
|
||||
override val name: String,
|
||||
default: MetaItem<*>?,
|
||||
override val descriptor: PropertyDescriptor,
|
||||
private val getter: suspend (before: MetaItem<*>?) -> MetaItem<*>,
|
||||
) : ReadOnlyDeviceProperty {
|
||||
|
||||
companion object {
|
||||
override val scope: CoroutineScope get() = this@DeviceBase.scope
|
||||
|
||||
private val state: MutableStateFlow<MetaItem<*>?> = MutableStateFlow(default)
|
||||
override val value: MetaItem<*>? get() = state.value
|
||||
|
||||
override suspend fun invalidate() {
|
||||
state.value = null
|
||||
}
|
||||
|
||||
override fun updateLogical(item: MetaItem<*>) {
|
||||
state.value = item
|
||||
notifyListeners {
|
||||
propertyChanged(name, item)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun read(force: Boolean): MetaItem<*> {
|
||||
//backup current value
|
||||
val currentValue = value
|
||||
return if (force || currentValue == null) {
|
||||
val res = withContext(scope.coroutineContext) {
|
||||
//all device operations should be run on device context
|
||||
//TODO add error catching
|
||||
getter(currentValue)
|
||||
}
|
||||
updateLogical(res)
|
||||
res
|
||||
} else {
|
||||
currentValue
|
||||
}
|
||||
}
|
||||
|
||||
override fun flow(): StateFlow<MetaItem<*>?> = state
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bound read-only property with given [getter]
|
||||
*/
|
||||
public fun newReadOnlyProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
): ReadOnlyDeviceProperty {
|
||||
val property = BasicReadOnlyDeviceProperty(
|
||||
name,
|
||||
default,
|
||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||
getter
|
||||
)
|
||||
registerProperty(name, property)
|
||||
return property
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private inner class BasicDeviceProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptor: PropertyDescriptor,
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?,
|
||||
) : BasicReadOnlyDeviceProperty(name, default, descriptor, getter), DeviceProperty {
|
||||
|
||||
override var value: MetaItem<*>?
|
||||
get() = super.value
|
||||
set(value) {
|
||||
scope.launch {
|
||||
if (value == null) {
|
||||
invalidate()
|
||||
} else {
|
||||
write(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val writeLock = Mutex()
|
||||
|
||||
override suspend fun write(item: MetaItem<*>) {
|
||||
writeLock.withLock {
|
||||
//fast return if value is not changed
|
||||
if (item == value) return@withLock
|
||||
val oldValue = value
|
||||
//all device operations should be run on device context
|
||||
withContext(scope.coroutineContext) {
|
||||
//TODO add error catching
|
||||
setter(oldValue, item)?.let {
|
||||
updateLogical(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bound mutable property with given [getter] and [setter]
|
||||
*/
|
||||
public fun newMutableProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?,
|
||||
): DeviceProperty {
|
||||
val property = BasicDeviceProperty(
|
||||
name,
|
||||
default,
|
||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||
getter,
|
||||
setter
|
||||
)
|
||||
registerProperty(name, property)
|
||||
return property
|
||||
}
|
||||
|
||||
/**
|
||||
* A stand-alone action
|
||||
*/
|
||||
private inner class BasicAction(
|
||||
override val name: String,
|
||||
override val descriptor: ActionDescriptor,
|
||||
private val block: suspend (MetaItem<*>?) -> MetaItem<*>?,
|
||||
) : Action {
|
||||
override suspend fun invoke(arg: MetaItem<*>?): MetaItem<*>? = block(arg).also {
|
||||
notifyListeners {
|
||||
actionExecuted(name, arg, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bound action
|
||||
*/
|
||||
public fun newAction(
|
||||
name: String,
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend (MetaItem<*>?) -> MetaItem<*>?,
|
||||
): Action {
|
||||
val action = BasicAction(name, ActionDescriptor(name).apply(descriptorBuilder), block)
|
||||
registerAction(name, action)
|
||||
return action
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,329 +0,0 @@
|
||||
package hep.dataforge.control.base
|
||||
|
||||
import hep.dataforge.control.api.PropertyDescriptor
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.asValue
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
private fun DeviceBase.propertyChanged(name: String, item: MetaItem<*>?){
|
||||
notifyListeners { propertyChanged(name, item) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A stand-alone [ReadOnlyDeviceProperty] implementation not directly attached to a device
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
public open class IsolatedReadOnlyDeviceProperty(
|
||||
override val name: String,
|
||||
default: MetaItem<*>?,
|
||||
override val descriptor: PropertyDescriptor,
|
||||
override val scope: CoroutineScope,
|
||||
private val callback: (name: String, item: MetaItem<*>) -> Unit,
|
||||
private val getter: suspend (before: MetaItem<*>?) -> MetaItem<*>
|
||||
) : ReadOnlyDeviceProperty {
|
||||
|
||||
private val state: MutableStateFlow<MetaItem<*>?> = MutableStateFlow(default)
|
||||
override val value: MetaItem<*>? get() = state.value
|
||||
|
||||
override suspend fun invalidate() {
|
||||
state.value = null
|
||||
}
|
||||
|
||||
override fun updateLogical(item: MetaItem<*>) {
|
||||
state.value = item
|
||||
callback(name, item)
|
||||
}
|
||||
|
||||
override suspend fun read(force: Boolean): MetaItem<*> {
|
||||
//backup current value
|
||||
val currentValue = value
|
||||
return if (force || currentValue == null) {
|
||||
val res = withContext(scope.coroutineContext) {
|
||||
//all device operations should be run on device context
|
||||
//TODO add error catching
|
||||
getter(currentValue)
|
||||
}
|
||||
updateLogical(res)
|
||||
res
|
||||
} else {
|
||||
currentValue
|
||||
}
|
||||
}
|
||||
|
||||
override fun flow(): StateFlow<MetaItem<*>?> = state
|
||||
}
|
||||
|
||||
public fun DeviceBase.readOnlyProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
||||
): ReadOnlyDeviceProperty = registerProperty(name) {
|
||||
IsolatedReadOnlyDeviceProperty(
|
||||
name,
|
||||
default,
|
||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||
scope,
|
||||
::propertyChanged,
|
||||
getter
|
||||
)
|
||||
}
|
||||
|
||||
private class ReadOnlyDevicePropertyDelegate<D : DeviceBase>(
|
||||
val owner: D,
|
||||
val default: MetaItem<*>?,
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
||||
) : ReadOnlyProperty<D, ReadOnlyDeviceProperty> {
|
||||
|
||||
override fun getValue(thisRef: D, property: KProperty<*>): ReadOnlyDeviceProperty {
|
||||
val name = property.name
|
||||
|
||||
return owner.registerProperty(name) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
IsolatedReadOnlyDeviceProperty(
|
||||
name,
|
||||
default,
|
||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||
owner.scope,
|
||||
owner::propertyChanged,
|
||||
getter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun <D : DeviceBase> D.reading(
|
||||
default: MetaItem<*>? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default,
|
||||
descriptorBuilder,
|
||||
getter
|
||||
)
|
||||
|
||||
public fun <D : DeviceBase> D.readingValue(
|
||||
default: Value? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> Any?
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it) },
|
||||
descriptorBuilder,
|
||||
getter = { MetaItem.ValueItem(Value.of(getter())) }
|
||||
)
|
||||
|
||||
public fun <D : DeviceBase> D.readingNumber(
|
||||
default: Number? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> Number
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
val number = getter()
|
||||
MetaItem.ValueItem(number.asValue())
|
||||
}
|
||||
)
|
||||
|
||||
public fun <D : DeviceBase> D.readingString(
|
||||
default: Number? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> String
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
val number = getter()
|
||||
MetaItem.ValueItem(number.asValue())
|
||||
}
|
||||
)
|
||||
|
||||
public fun <D : DeviceBase> D.readingMeta(
|
||||
default: Meta? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend MetaBuilder.() -> Unit
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default?.let { MetaItem.NodeItem(it) },
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
MetaItem.NodeItem(MetaBuilder().apply { getter() })
|
||||
}
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
public class IsolatedDeviceProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptor: PropertyDescriptor,
|
||||
scope: CoroutineScope,
|
||||
updateCallback: (name: String, item: MetaItem<*>?) -> Unit,
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||
) : IsolatedReadOnlyDeviceProperty(name, default, descriptor, scope, updateCallback, getter), DeviceProperty {
|
||||
|
||||
override var value: MetaItem<*>?
|
||||
get() = super.value
|
||||
set(value) {
|
||||
scope.launch {
|
||||
if (value == null) {
|
||||
invalidate()
|
||||
} else {
|
||||
write(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val writeLock = Mutex()
|
||||
|
||||
override suspend fun write(item: MetaItem<*>) {
|
||||
writeLock.withLock {
|
||||
//fast return if value is not changed
|
||||
if (item == value) return@withLock
|
||||
val oldValue = value
|
||||
//all device operations should be run on device context
|
||||
withContext(scope.coroutineContext) {
|
||||
//TODO add error catching
|
||||
setter(oldValue, item)?.let {
|
||||
updateLogical(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun DeviceBase.mutableProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||
): DeviceProperty = registerMutableProperty(name) {
|
||||
IsolatedDeviceProperty(
|
||||
name,
|
||||
default,
|
||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||
scope,
|
||||
::propertyChanged,
|
||||
getter,
|
||||
setter
|
||||
)
|
||||
}
|
||||
|
||||
private class DevicePropertyDelegate<D : DeviceBase>(
|
||||
val owner: D,
|
||||
val default: MetaItem<*>?,
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||
) : ReadOnlyProperty<D, DeviceProperty> {
|
||||
|
||||
override fun getValue(thisRef: D, property: KProperty<*>): IsolatedDeviceProperty {
|
||||
val name = property.name
|
||||
return owner.registerMutableProperty(name) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
IsolatedDeviceProperty(
|
||||
name,
|
||||
default,
|
||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||
owner.scope,
|
||||
owner::propertyChanged,
|
||||
getter,
|
||||
setter
|
||||
)
|
||||
} as IsolatedDeviceProperty
|
||||
}
|
||||
}
|
||||
|
||||
public fun <D : DeviceBase> D.writing(
|
||||
default: MetaItem<*>? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||
): ReadOnlyProperty<D, DeviceProperty> = DevicePropertyDelegate(
|
||||
this,
|
||||
default,
|
||||
descriptorBuilder,
|
||||
getter,
|
||||
setter
|
||||
)
|
||||
|
||||
public fun <D : DeviceBase> D.writingVirtual(
|
||||
default: MetaItem<*>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): ReadOnlyProperty<D, DeviceProperty> = writing(
|
||||
default,
|
||||
descriptorBuilder,
|
||||
getter = { it ?: default },
|
||||
setter = { _, newItem -> newItem }
|
||||
)
|
||||
|
||||
public fun <D : DeviceBase> D.writingVirtual(
|
||||
default: Value,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): ReadOnlyProperty<D, DeviceProperty> = writing(
|
||||
MetaItem.ValueItem(default),
|
||||
descriptorBuilder,
|
||||
getter = { it ?: MetaItem.ValueItem(default) },
|
||||
setter = { _, newItem -> newItem }
|
||||
)
|
||||
|
||||
public fun <D : DeviceBase> D.writingDouble(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (Double) -> Double,
|
||||
setter: suspend (oldValue: Double?, newValue: Double) -> Double?
|
||||
): ReadOnlyProperty<D, DeviceProperty> {
|
||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||
MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue())
|
||||
}
|
||||
|
||||
val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue ->
|
||||
setter(oldValue.double, newValue.double ?: Double.NaN)?.asMetaItem()
|
||||
}
|
||||
|
||||
return DevicePropertyDelegate(
|
||||
this,
|
||||
MetaItem.ValueItem(Double.NaN.asValue()),
|
||||
descriptorBuilder,
|
||||
innerGetter,
|
||||
innerSetter
|
||||
)
|
||||
}
|
||||
|
||||
public fun <D : DeviceBase> D.writingBoolean(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (Boolean?) -> Boolean,
|
||||
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?
|
||||
): ReadOnlyProperty<D, DeviceProperty> {
|
||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||
MetaItem.ValueItem(getter(it.boolean).asValue())
|
||||
}
|
||||
|
||||
val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue ->
|
||||
setter(oldValue.boolean, newValue.boolean?: error("Can't convert $newValue to boolean"))?.asValue()?.asMetaItem()
|
||||
}
|
||||
|
||||
return DevicePropertyDelegate(
|
||||
this,
|
||||
MetaItem.ValueItem(Null),
|
||||
descriptorBuilder,
|
||||
innerGetter,
|
||||
innerSetter
|
||||
)
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package hep.dataforge.control.base
|
||||
|
||||
import hep.dataforge.control.api.ActionDescriptor
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.values.Value
|
||||
import kotlin.properties.PropertyDelegateProvider
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
private fun <D : DeviceBase> D.provideAction(): ReadOnlyProperty<D, Action> =
|
||||
ReadOnlyProperty { _: D, property: KProperty<*> ->
|
||||
val name = property.name
|
||||
return@ReadOnlyProperty actions[name]!!
|
||||
}
|
||||
|
||||
public typealias ActionDelegate = ReadOnlyProperty<DeviceBase, Action>
|
||||
|
||||
private class ActionProvider<D : DeviceBase>(
|
||||
val owner: D,
|
||||
val descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
val block: suspend (MetaItem<*>?) -> MetaItem<*>?,
|
||||
) : PropertyDelegateProvider<D, ActionDelegate> {
|
||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ActionDelegate {
|
||||
val name = property.name
|
||||
owner.newAction(name, descriptorBuilder, block)
|
||||
return owner.provideAction()
|
||||
}
|
||||
}
|
||||
|
||||
public fun DeviceBase.requesting(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend (MetaItem<*>?) -> MetaItem<*>?,
|
||||
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder, block)
|
||||
|
||||
public fun <D : DeviceBase> D.requestingValue(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend (MetaItem<*>?) -> Any?,
|
||||
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||
val res = block(it)
|
||||
MetaItem.ValueItem(Value.of(res))
|
||||
}
|
||||
|
||||
public fun <D : DeviceBase> D.requestingMeta(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend MetaBuilder.(MetaItem<*>?) -> Unit,
|
||||
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||
val res = MetaBuilder().apply { block(it) }
|
||||
MetaItem.NodeItem(res)
|
||||
}
|
||||
|
||||
public fun DeviceBase.acting(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
block: suspend (MetaItem<*>?) -> Unit,
|
||||
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||
block(it)
|
||||
null
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
package hep.dataforge.control.base
|
||||
|
||||
import hep.dataforge.control.api.PropertyDescriptor
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.asValue
|
||||
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]!!
|
||||
}
|
||||
|
||||
public typealias ReadOnlyPropertyDelegate = ReadOnlyProperty<DeviceBase, ReadOnlyDeviceProperty>
|
||||
|
||||
private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
|
||||
val owner: D,
|
||||
val default: MetaItem<*>?,
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
) : PropertyDelegateProvider<D, ReadOnlyPropertyDelegate> {
|
||||
|
||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate {
|
||||
val name = property.name
|
||||
owner.newReadOnlyProperty(name, default, descriptorBuilder, getter)
|
||||
return owner.provideProperty()
|
||||
}
|
||||
}
|
||||
|
||||
public fun DeviceBase.reading(
|
||||
default: MetaItem<*>? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default,
|
||||
descriptorBuilder,
|
||||
getter
|
||||
)
|
||||
|
||||
public fun DeviceBase.readingValue(
|
||||
default: Value? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> Any?,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it) },
|
||||
descriptorBuilder,
|
||||
getter = { MetaItem.ValueItem(Value.of(getter())) }
|
||||
)
|
||||
|
||||
public fun DeviceBase.readingNumber(
|
||||
default: Number? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> Number,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
val number = getter()
|
||||
MetaItem.ValueItem(number.asValue())
|
||||
}
|
||||
)
|
||||
|
||||
public fun DeviceBase.readingString(
|
||||
default: Number? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> String,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
val number = getter()
|
||||
MetaItem.ValueItem(number.asValue())
|
||||
}
|
||||
)
|
||||
|
||||
public fun DeviceBase.readingMeta(
|
||||
default: Meta? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend MetaBuilder.() -> Unit,
|
||||
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||
this,
|
||||
default?.let { MetaItem.NodeItem(it) },
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
MetaItem.NodeItem(MetaBuilder().apply { getter() })
|
||||
}
|
||||
)
|
||||
|
||||
private fun DeviceBase.provideMutableProperty(): ReadOnlyProperty<DeviceBase, DeviceProperty> =
|
||||
ReadOnlyProperty { _: DeviceBase, property: KProperty<*> ->
|
||||
val name = property.name
|
||||
return@ReadOnlyProperty properties[name] as DeviceProperty
|
||||
}
|
||||
|
||||
public typealias PropertyDelegate = ReadOnlyProperty<DeviceBase, DeviceProperty>
|
||||
|
||||
private class DevicePropertyProvider<D : DeviceBase>(
|
||||
val owner: D,
|
||||
val default: MetaItem<*>?,
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?,
|
||||
) : PropertyDelegateProvider<D, PropertyDelegate> {
|
||||
|
||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate {
|
||||
val name = property.name
|
||||
owner.newMutableProperty(name, default, descriptorBuilder, getter, setter)
|
||||
return owner.provideMutableProperty()
|
||||
}
|
||||
}
|
||||
|
||||
public fun DeviceBase.writing(
|
||||
default: MetaItem<*>? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?,
|
||||
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = DevicePropertyProvider(
|
||||
this,
|
||||
default,
|
||||
descriptorBuilder,
|
||||
getter,
|
||||
setter
|
||||
)
|
||||
|
||||
public fun DeviceBase.writingVirtual(
|
||||
default: MetaItem<*>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
||||
default,
|
||||
descriptorBuilder,
|
||||
getter = { it ?: default },
|
||||
setter = { _, newItem -> newItem }
|
||||
)
|
||||
|
||||
public fun DeviceBase.writingVirtual(
|
||||
default: Value,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
||||
MetaItem.ValueItem(default),
|
||||
descriptorBuilder,
|
||||
getter = { it ?: MetaItem.ValueItem(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> {
|
||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||
MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue())
|
||||
}
|
||||
|
||||
val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue ->
|
||||
setter(oldValue.double, newValue.double ?: Double.NaN)?.asMetaItem()
|
||||
}
|
||||
|
||||
return DevicePropertyProvider(
|
||||
this,
|
||||
MetaItem.ValueItem(Double.NaN.asValue()),
|
||||
descriptorBuilder,
|
||||
innerGetter,
|
||||
innerSetter
|
||||
)
|
||||
}
|
||||
|
||||
public fun <D : DeviceBase> D.writingBoolean(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (Boolean?) -> Boolean,
|
||||
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?,
|
||||
): PropertyDelegateProvider<D, PropertyDelegate> {
|
||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||
MetaItem.ValueItem(getter(it.boolean).asValue())
|
||||
}
|
||||
|
||||
val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue ->
|
||||
setter(oldValue.boolean, newValue.boolean ?: error("Can't convert $newValue to boolean"))?.asValue()
|
||||
?.asMetaItem()
|
||||
}
|
||||
|
||||
return DevicePropertyProvider(
|
||||
this,
|
||||
MetaItem.ValueItem(Null),
|
||||
descriptorBuilder,
|
||||
innerGetter,
|
||||
innerSetter
|
||||
)
|
||||
}
|
@ -78,7 +78,7 @@ class DeviceController(
|
||||
} else if (target != null && target != deviceTarget) {
|
||||
error("Wrong target name $deviceTarget expected but $target found")
|
||||
} else {
|
||||
val response = device.respond(request).apply {
|
||||
val response = device.respondWithData(request).apply {
|
||||
meta {
|
||||
"target" put request.meta["source"].string
|
||||
"source" put deviceTarget
|
||||
|
@ -8,6 +8,7 @@ import hep.dataforge.values.Null
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.time.Duration
|
||||
|
||||
operator fun ReadOnlyDeviceProperty.getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*> =
|
||||
value ?: MetaItem.ValueItem(Null)
|
||||
@ -17,10 +18,8 @@ operator fun DeviceProperty.setValue(thisRef: Any?, property: KProperty<*>, valu
|
||||
}
|
||||
|
||||
fun <T : Any> ReadOnlyDeviceProperty.convert(metaConverter: MetaConverter<T>): ReadOnlyProperty<Any?, T> {
|
||||
return object : ReadOnlyProperty<Any?, T> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
return this@convert.getValue(thisRef, property).let { metaConverter.itemToObject(it) }
|
||||
}
|
||||
return ReadOnlyProperty { thisRef, property ->
|
||||
getValue(thisRef, property).let { metaConverter.itemToObject(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,3 +43,6 @@ fun DeviceProperty.int() = convert(MetaConverter.int)
|
||||
|
||||
fun ReadOnlyDeviceProperty.string() = convert(MetaConverter.string)
|
||||
fun DeviceProperty.string() = convert(MetaConverter.string)
|
||||
|
||||
fun ReadOnlyDeviceProperty.duration(): ReadOnlyProperty<Any?, Duration> = TODO()
|
||||
fun DeviceProperty.duration(): ReadWriteProperty<Any?, Duration> = TODO()
|
@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id("kscience.jvm")
|
||||
id("kscience.publish")
|
||||
id("ru.mipt.npm.jvm")
|
||||
id("ru.mipt.npm.publish")
|
||||
}
|
||||
|
||||
dependencies{
|
||||
|
@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id("kscience.jvm")
|
||||
id("kscience.publish")
|
||||
id("ru.mipt.npm.jvm")
|
||||
id("ru.mipt.npm.publish")
|
||||
}
|
||||
|
||||
kscience {
|
||||
@ -8,7 +8,7 @@ kscience {
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
val ktorVersion: String by extra("1.3.2")
|
||||
val ktorVersion: String by extra("1.4.0")
|
||||
|
||||
dependencies{
|
||||
implementation(project(":dataforge-device-core"))
|
||||
|
@ -6,6 +6,7 @@ plugins {
|
||||
|
||||
|
||||
repositories{
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven("https://kotlin.bintray.com/kotlinx")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
@ -21,7 +22,7 @@ dependencies{
|
||||
implementation(project(":dataforge-device-client"))
|
||||
implementation("no.tornado:tornadofx:1.7.20")
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
implementation("kscience.plotlykt:plotlykt-server:0.2.0")
|
||||
implementation("kscience.plotlykt:plotlykt-server:0.3.0-dev-2")
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
|
@ -50,7 +50,7 @@ class DemoDevice(parentScope: CoroutineScope) : DeviceBase() {
|
||||
}
|
||||
|
||||
|
||||
val resetScale: Action by action {
|
||||
val resetScale: Action by acting {
|
||||
timeScaleValue = 5000.0
|
||||
sinScaleValue = 1.0
|
||||
cosScaleValue = 1.0
|
||||
|
BIN
docs/schemes/direct-vs-loop.vsdx
Normal file
BIN
docs/schemes/direct-vs-loop.vsdx
Normal file
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id("kscience.jvm")
|
||||
id("kscience.publish")
|
||||
id("ru.mipt.npm.jvm")
|
||||
id("ru.mipt.npm.publish")
|
||||
}
|
||||
|
||||
//TODO to be moved to a separate project
|
||||
|
@ -1,11 +1,14 @@
|
||||
package ru.mipt.npm.devices.pimotionmaster
|
||||
|
||||
import hep.dataforge.control.api.DeviceHub
|
||||
import hep.dataforge.control.base.*
|
||||
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.names.NameToken
|
||||
import hep.dataforge.values.Null
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
@ -14,11 +17,15 @@ import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
||||
public class PiMotionMasterDevice(
|
||||
parentScope: CoroutineScope,
|
||||
axes: List<String>,
|
||||
private val portFactory: suspend (MetaItem<*>?) -> Port,
|
||||
) : DeviceBase() {
|
||||
) : DeviceBase(), DeviceHub {
|
||||
|
||||
override val scope: CoroutineScope = CoroutineScope(
|
||||
parentScope.coroutineContext + Job(parentScope.coroutineContext[Job])
|
||||
@ -28,11 +35,17 @@ public class PiMotionMasterDevice(
|
||||
info = "The port for TCP connector"
|
||||
}
|
||||
|
||||
public val timeout: DeviceProperty by writingVirtual(Null) {
|
||||
info = "Timeout"
|
||||
}
|
||||
|
||||
public var timeoutValue: Duration by timeout.duration()
|
||||
|
||||
private val connector = PortProxy { portFactory(port.value) }
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
private suspend fun sendCommand(command: String, vararg arguments: String) {
|
||||
private suspend fun sendCommandInternal(command: String, vararg arguments: String) {
|
||||
val joinedArguments = if (arguments.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
@ -46,9 +59,11 @@ public class PiMotionMasterDevice(
|
||||
* Send a synchronous request and receive a list of lines as a response
|
||||
*/
|
||||
private suspend fun request(command: String, vararg arguments: String): List<String> = mutex.withLock {
|
||||
sendCommand(command, *arguments)
|
||||
val phrases = connector.receiving().withDelimiter("\n")
|
||||
return@withLock phrases.takeWhile { it.endsWith(" \n") }.toList() + phrases.first()
|
||||
withTimeout(timeoutValue) {
|
||||
sendCommandInternal(command, *arguments)
|
||||
val phrases = connector.receiving().withDelimiter("\n")
|
||||
phrases.takeWhile { it.endsWith(" \n") }.toList() + phrases.first()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestAndParse(command: String, vararg arguments: String): Map<String, String> = buildMap {
|
||||
@ -63,11 +78,13 @@ public class PiMotionMasterDevice(
|
||||
*/
|
||||
private suspend fun send(command: String, vararg arguments: String) {
|
||||
mutex.withLock {
|
||||
sendCommand(command, *arguments)
|
||||
withTimeout(timeoutValue) {
|
||||
sendCommandInternal(command, *arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public val initialize: Action by action {
|
||||
public val initialize: Action by acting {
|
||||
send("INI")
|
||||
}
|
||||
|
||||
@ -79,32 +96,37 @@ public class PiMotionMasterDevice(
|
||||
override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope
|
||||
public val enabled: DeviceProperty by writingBoolean<Axis>(
|
||||
getter = {
|
||||
val result = requestAndParse("EAX?", axisId)[axisId]?.toIntOrNull()
|
||||
?: error("Malformed response. Should include integer value for $axisId")
|
||||
result != 0
|
||||
val eax = requestAndParse("EAX?", axisId)[axisId]?.toIntOrNull()
|
||||
?: error("Malformed EAX response. Should include integer value for $axisId")
|
||||
eax != 0
|
||||
},
|
||||
setter = { oldValue, newValue ->
|
||||
val value = if(newValue){
|
||||
setter = { _, newValue ->
|
||||
val value = if (newValue) {
|
||||
"1"
|
||||
} else {
|
||||
"0"
|
||||
}
|
||||
send("EAX", axisId, value)
|
||||
oldValue
|
||||
newValue
|
||||
}
|
||||
)
|
||||
|
||||
public val halt: Action by action {
|
||||
public val halt: Action by acting {
|
||||
send("HLT", axisId)
|
||||
}
|
||||
|
||||
public val targetPosition: DeviceProperty by writingDouble<Axis>(
|
||||
getter = {
|
||||
requestAndParse("MOV?", axisId)[axisId]?.toDoubleOrNull()
|
||||
?: error("Malformed MOV response. Should include float value for $axisId")
|
||||
},
|
||||
setter = { _, newValue ->
|
||||
send("MOV", axisId, newValue.toString())
|
||||
newValue
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
//list everything here to ensure it is initialized
|
||||
initialize
|
||||
firmwareVersion
|
||||
|
||||
}
|
||||
|
||||
override val devices: Map<NameToken, Axis> = axes.associate { NameToken(it) to Axis(it) }
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
pluginManagement {
|
||||
val kotlinVersion = "1.4.0"
|
||||
val toolsVersion = "0.6.0-dev-1"
|
||||
val toolsVersion = "0.6.0-dev-4"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@ -9,25 +9,17 @@ pluginManagement {
|
||||
maven("https://kotlin.bintray.com/kotlinx")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
maven("https://dl.bintray.com/mipt-npm/dataforge")
|
||||
maven("https://dl.bintray.com/mipt-npm/scientifik")
|
||||
maven("https://dl.bintray.com/mipt-npm/kscience")
|
||||
maven("https://dl.bintray.com/mipt-npm/dev")
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("ru.mipt.npm.mpp") version toolsVersion
|
||||
id("ru.mipt.npm.jvm") version toolsVersion
|
||||
id("ru.mipt.npm.js") version toolsVersion
|
||||
id("ru.mipt.npm.publish") version toolsVersion
|
||||
kotlin("jvm") version kotlinVersion
|
||||
id("scientifik.mpp") version toolsVersion
|
||||
id("scientifik.jvm") version toolsVersion
|
||||
id("scientifik.js") version toolsVersion
|
||||
id("scientifik.publish") version toolsVersion
|
||||
}
|
||||
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
when (requested.id.id) {
|
||||
"kscience.publish", "kscience.mpp", "kscience.jvm", "kscience.js" -> useModule("ru.mipt.npm:gradle-tools:${toolsVersion}")
|
||||
"kotlinx-atomicfu" -> useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}")
|
||||
}
|
||||
}
|
||||
kotlin("js") version kotlinVersion
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user