Properties refactoring
This commit is contained in:
parent
0e6363048e
commit
7413bda5a1
@ -45,6 +45,10 @@ abstract class DeviceBase : Device {
|
|||||||
return properties.getOrPut(name, builder)
|
return properties.getOrPut(name, builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun registerMutableProperty(name: String, builder: () -> DeviceProperty): DeviceProperty {
|
||||||
|
return properties.getOrPut(name, builder) as DeviceProperty
|
||||||
|
}
|
||||||
|
|
||||||
internal fun registerAction(name: String, builder: () -> Action): Action {
|
internal fun registerAction(name: String, builder: () -> Action): Action {
|
||||||
return actions.getOrPut(name, builder)
|
return actions.getOrPut(name, builder)
|
||||||
}
|
}
|
||||||
|
@ -9,52 +9,52 @@ import kotlin.time.Duration
|
|||||||
/**
|
/**
|
||||||
* Read-only device property
|
* Read-only device property
|
||||||
*/
|
*/
|
||||||
interface ReadOnlyDeviceProperty {
|
public interface ReadOnlyDeviceProperty {
|
||||||
/**
|
/**
|
||||||
* Property name, should be unique in device
|
* Property name, should be unique in device
|
||||||
*/
|
*/
|
||||||
val name: String
|
public val name: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property descriptor
|
* Property descriptor
|
||||||
*/
|
*/
|
||||||
val descriptor: PropertyDescriptor
|
public val descriptor: PropertyDescriptor
|
||||||
|
|
||||||
val scope: CoroutineScope
|
public val scope: CoroutineScope
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erase logical value and force re-read from device on next [read]
|
* Erase logical value and force re-read from device on next [read]
|
||||||
*/
|
*/
|
||||||
suspend fun invalidate()
|
public suspend fun invalidate()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directly update property logical value and notify listener without writing it to device
|
||||||
|
*/
|
||||||
|
public fun updateLogical(item: MetaItem<*>)
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Update property logical value and notify listener without writing it to device
|
|
||||||
// */
|
|
||||||
// suspend fun update(item: MetaItem<*>)
|
|
||||||
//
|
|
||||||
/**
|
/**
|
||||||
* Get cached value and return null if value is invalid or not initialized
|
* Get cached value and return null if value is invalid or not initialized
|
||||||
*/
|
*/
|
||||||
val value: MetaItem<*>?
|
public val value: MetaItem<*>?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read value either from cache if cache is valid or directly from physical device.
|
* Read value either from cache if cache is valid or directly from physical device.
|
||||||
* If [force], reread
|
* If [force], reread from physical state even if the logical state is set.
|
||||||
*/
|
*/
|
||||||
suspend fun read(force: Boolean = false): MetaItem<*>
|
public suspend fun read(force: Boolean = false): MetaItem<*>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [Flow] representing future logical states of the property.
|
* The [Flow] representing future logical states of the property.
|
||||||
* Produces null when the state is invalidated
|
* Produces null when the state is invalidated
|
||||||
*/
|
*/
|
||||||
fun flow(): Flow<MetaItem<*>?>
|
public fun flow(): Flow<MetaItem<*>?>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch recurring force re-read job on a property scope with given [duration] between reads.
|
* Launch recurring force re-read job on a property scope with given [duration] between reads.
|
||||||
*/
|
*/
|
||||||
fun ReadOnlyDeviceProperty.readEvery(duration: Duration): Job = scope.launch {
|
public fun ReadOnlyDeviceProperty.readEvery(duration: Duration): Job = scope.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
read(true)
|
read(true)
|
||||||
delay(duration)
|
delay(duration)
|
||||||
@ -64,11 +64,11 @@ fun ReadOnlyDeviceProperty.readEvery(duration: Duration): Job = scope.launch {
|
|||||||
/**
|
/**
|
||||||
* A writeable device property with non-suspended write
|
* A writeable device property with non-suspended write
|
||||||
*/
|
*/
|
||||||
interface DeviceProperty : ReadOnlyDeviceProperty {
|
public interface DeviceProperty : ReadOnlyDeviceProperty {
|
||||||
override var value: MetaItem<*>?
|
override var value: MetaItem<*>?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write value to physical device. Invalidates logical value, but does not update it automatically
|
* Write value to physical device. Invalidates logical value, but does not update it automatically
|
||||||
*/
|
*/
|
||||||
suspend fun write(item: MetaItem<*>)
|
public suspend fun write(item: MetaItem<*>)
|
||||||
}
|
}
|
@ -1,10 +1,8 @@
|
|||||||
package hep.dataforge.control.base
|
package hep.dataforge.control.base
|
||||||
|
|
||||||
import hep.dataforge.control.api.PropertyDescriptor
|
import hep.dataforge.control.api.PropertyDescriptor
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.MetaBuilder
|
import hep.dataforge.values.Null
|
||||||
import hep.dataforge.meta.MetaItem
|
|
||||||
import hep.dataforge.meta.double
|
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
import hep.dataforge.values.asValue
|
import hep.dataforge.values.asValue
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -26,7 +24,7 @@ private fun DeviceBase.propertyChanged(name: String, item: MetaItem<*>?){
|
|||||||
* A stand-alone [ReadOnlyDeviceProperty] implementation not directly attached to a device
|
* A stand-alone [ReadOnlyDeviceProperty] implementation not directly attached to a device
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
open class IsolatedReadOnlyDeviceProperty(
|
public open class IsolatedReadOnlyDeviceProperty(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
default: MetaItem<*>?,
|
default: MetaItem<*>?,
|
||||||
override val descriptor: PropertyDescriptor,
|
override val descriptor: PropertyDescriptor,
|
||||||
@ -42,7 +40,7 @@ open class IsolatedReadOnlyDeviceProperty(
|
|||||||
state.value = null
|
state.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun update(item: MetaItem<*>) {
|
override fun updateLogical(item: MetaItem<*>) {
|
||||||
state.value = item
|
state.value = item
|
||||||
callback(name, item)
|
callback(name, item)
|
||||||
}
|
}
|
||||||
@ -56,7 +54,7 @@ open class IsolatedReadOnlyDeviceProperty(
|
|||||||
//TODO add error catching
|
//TODO add error catching
|
||||||
getter(currentValue)
|
getter(currentValue)
|
||||||
}
|
}
|
||||||
update(res)
|
updateLogical(res)
|
||||||
res
|
res
|
||||||
} else {
|
} else {
|
||||||
currentValue
|
currentValue
|
||||||
@ -66,7 +64,7 @@ open class IsolatedReadOnlyDeviceProperty(
|
|||||||
override fun flow(): StateFlow<MetaItem<*>?> = state
|
override fun flow(): StateFlow<MetaItem<*>?> = state
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DeviceBase.readOnlyProperty(
|
public fun DeviceBase.readOnlyProperty(
|
||||||
name: String,
|
name: String,
|
||||||
default: MetaItem<*>?,
|
default: MetaItem<*>?,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
@ -87,9 +85,9 @@ private class ReadOnlyDevicePropertyDelegate<D : DeviceBase>(
|
|||||||
val default: MetaItem<*>?,
|
val default: MetaItem<*>?,
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
||||||
) : ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> {
|
) : ReadOnlyProperty<D, ReadOnlyDeviceProperty> {
|
||||||
|
|
||||||
override fun getValue(thisRef: D, property: KProperty<*>): IsolatedReadOnlyDeviceProperty {
|
override fun getValue(thisRef: D, property: KProperty<*>): ReadOnlyDeviceProperty {
|
||||||
val name = property.name
|
val name = property.name
|
||||||
|
|
||||||
return owner.registerProperty(name) {
|
return owner.registerProperty(name) {
|
||||||
@ -102,37 +100,37 @@ private class ReadOnlyDevicePropertyDelegate<D : DeviceBase>(
|
|||||||
owner::propertyChanged,
|
owner::propertyChanged,
|
||||||
getter
|
getter
|
||||||
)
|
)
|
||||||
} as IsolatedReadOnlyDeviceProperty
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <D : DeviceBase> D.reading(
|
public fun <D : DeviceBase> D.reading(
|
||||||
default: MetaItem<*>? = null,
|
default: MetaItem<*>? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
||||||
): ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||||
this,
|
this,
|
||||||
default,
|
default,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter
|
getter
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.readingValue(
|
public fun <D : DeviceBase> D.readingValue(
|
||||||
default: Value? = null,
|
default: Value? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend () -> Any
|
getter: suspend () -> Any?
|
||||||
): ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItem.ValueItem(it) },
|
default?.let { MetaItem.ValueItem(it) },
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = { MetaItem.ValueItem(Value.of(getter())) }
|
getter = { MetaItem.ValueItem(Value.of(getter())) }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.readingNumber(
|
public fun <D : DeviceBase> D.readingNumber(
|
||||||
default: Number? = null,
|
default: Number? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend () -> Number
|
getter: suspend () -> Number
|
||||||
): ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
@ -142,11 +140,25 @@ fun <D : DeviceBase> D.readingNumber(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.readingMeta(
|
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,
|
default: Meta? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend MetaBuilder.() -> Unit
|
getter: suspend MetaBuilder.() -> Unit
|
||||||
): ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||||
this,
|
this,
|
||||||
default?.let { MetaItem.NodeItem(it) },
|
default?.let { MetaItem.NodeItem(it) },
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
@ -156,7 +168,7 @@ fun <D : DeviceBase> D.readingMeta(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class IsolatedDeviceProperty(
|
public class IsolatedDeviceProperty(
|
||||||
name: String,
|
name: String,
|
||||||
default: MetaItem<*>?,
|
default: MetaItem<*>?,
|
||||||
descriptor: PropertyDescriptor,
|
descriptor: PropertyDescriptor,
|
||||||
@ -189,20 +201,20 @@ class IsolatedDeviceProperty(
|
|||||||
withContext(scope.coroutineContext) {
|
withContext(scope.coroutineContext) {
|
||||||
//TODO add error catching
|
//TODO add error catching
|
||||||
setter(oldValue, item)?.let {
|
setter(oldValue, item)?.let {
|
||||||
update(it)
|
updateLogical(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DeviceBase.mutableProperty(
|
public fun DeviceBase.mutableProperty(
|
||||||
name: String,
|
name: String,
|
||||||
default: MetaItem<*>?,
|
default: MetaItem<*>?,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||||
): ReadOnlyDeviceProperty = registerProperty(name) {
|
): DeviceProperty = registerMutableProperty(name) {
|
||||||
IsolatedDeviceProperty(
|
IsolatedDeviceProperty(
|
||||||
name,
|
name,
|
||||||
default,
|
default,
|
||||||
@ -220,11 +232,11 @@ private class DevicePropertyDelegate<D : DeviceBase>(
|
|||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||||
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||||
) : ReadOnlyProperty<D, IsolatedDeviceProperty> {
|
) : ReadOnlyProperty<D, DeviceProperty> {
|
||||||
|
|
||||||
override fun getValue(thisRef: D, property: KProperty<*>): IsolatedDeviceProperty {
|
override fun getValue(thisRef: D, property: KProperty<*>): IsolatedDeviceProperty {
|
||||||
val name = property.name
|
val name = property.name
|
||||||
return owner.registerProperty(name) {
|
return owner.registerMutableProperty(name) {
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
IsolatedDeviceProperty(
|
IsolatedDeviceProperty(
|
||||||
name,
|
name,
|
||||||
@ -239,12 +251,12 @@ private class DevicePropertyDelegate<D : DeviceBase>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <D : DeviceBase> D.writing(
|
public fun <D : DeviceBase> D.writing(
|
||||||
default: MetaItem<*>? = null,
|
default: MetaItem<*>? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||||
): ReadOnlyProperty<D, IsolatedDeviceProperty> = DevicePropertyDelegate(
|
): ReadOnlyProperty<D, DeviceProperty> = DevicePropertyDelegate(
|
||||||
this,
|
this,
|
||||||
default,
|
default,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
@ -252,31 +264,31 @@ fun <D : DeviceBase> D.writing(
|
|||||||
setter
|
setter
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.writingVirtual(
|
public fun <D : DeviceBase> D.writingVirtual(
|
||||||
default: MetaItem<*>,
|
default: MetaItem<*>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
): ReadOnlyProperty<D, IsolatedDeviceProperty> = writing(
|
): ReadOnlyProperty<D, DeviceProperty> = writing(
|
||||||
default,
|
default,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = { it ?: default },
|
getter = { it ?: default },
|
||||||
setter = { _, newItem -> newItem }
|
setter = { _, newItem -> newItem }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.writingVirtual(
|
public fun <D : DeviceBase> D.writingVirtual(
|
||||||
default: Value,
|
default: Value,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
): ReadOnlyProperty<D, IsolatedDeviceProperty> = writing(
|
): ReadOnlyProperty<D, DeviceProperty> = writing(
|
||||||
MetaItem.ValueItem(default),
|
MetaItem.ValueItem(default),
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = { it ?: MetaItem.ValueItem(default) },
|
getter = { it ?: MetaItem.ValueItem(default) },
|
||||||
setter = { _, newItem -> newItem }
|
setter = { _, newItem -> newItem }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.writingDouble(
|
public fun <D : DeviceBase> D.writingDouble(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (Double) -> Double,
|
getter: suspend (Double) -> Double,
|
||||||
setter: suspend (oldValue: Double?, newValue: Double) -> Double?
|
setter: suspend (oldValue: Double?, newValue: Double) -> Double?
|
||||||
): ReadOnlyProperty<D, IsolatedDeviceProperty> {
|
): ReadOnlyProperty<D, DeviceProperty> {
|
||||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||||
MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue())
|
MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue())
|
||||||
}
|
}
|
||||||
@ -292,4 +304,26 @@ fun <D : DeviceBase> D.writingDouble(
|
|||||||
innerGetter,
|
innerGetter,
|
||||||
innerSetter
|
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
|
||||||
|
)
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ import hep.dataforge.control.api.DeviceHub
|
|||||||
import hep.dataforge.control.api.DeviceListener
|
import hep.dataforge.control.api.DeviceListener
|
||||||
import hep.dataforge.control.api.get
|
import hep.dataforge.control.api.get
|
||||||
import hep.dataforge.control.controllers.DeviceMessage.Companion.PROPERTY_CHANGED_ACTION
|
import hep.dataforge.control.controllers.DeviceMessage.Companion.PROPERTY_CHANGED_ACTION
|
||||||
|
import hep.dataforge.io.Consumer
|
||||||
import hep.dataforge.io.Envelope
|
import hep.dataforge.io.Envelope
|
||||||
import hep.dataforge.io.Responder
|
import hep.dataforge.io.Responder
|
||||||
import hep.dataforge.io.SimpleEnvelope
|
import hep.dataforge.io.SimpleEnvelope
|
||||||
@ -168,7 +169,7 @@ class DeviceController(
|
|||||||
suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage {
|
suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage {
|
||||||
return try {
|
return try {
|
||||||
val targetName = request.target?.toName() ?: Name.EMPTY
|
val targetName = request.target?.toName() ?: Name.EMPTY
|
||||||
val device = this[targetName]
|
val device = this[targetName] ?: error("The device with name $targetName not found in $this")
|
||||||
DeviceController.respondMessage(device, targetName.toString(), request)
|
DeviceController.respondMessage(device, targetName.toString(), request)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
DeviceMessage.fail {
|
DeviceMessage.fail {
|
||||||
|
@ -8,6 +8,7 @@ import hep.dataforge.control.api.Device
|
|||||||
import hep.dataforge.control.api.DeviceHub
|
import hep.dataforge.control.api.DeviceHub
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class DeviceManager : AbstractPlugin(), DeviceHub {
|
class DeviceManager : AbstractPlugin(), DeviceHub {
|
||||||
@ -16,14 +17,14 @@ class DeviceManager : AbstractPlugin(), DeviceHub {
|
|||||||
/**
|
/**
|
||||||
* Actual list of connected devices
|
* Actual list of connected devices
|
||||||
*/
|
*/
|
||||||
private val top = HashMap<Name, Device>()
|
private val top = HashMap<NameToken, Device>()
|
||||||
override val devices: Map<Name, Device> get() = top
|
override val devices: Map<NameToken, Device> get() = top
|
||||||
|
|
||||||
val controller by lazy {
|
val controller by lazy {
|
||||||
HubController(this, context)
|
HubController(this, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerDevice(name: Name, device: Device) {
|
fun registerDevice(name: NameToken, device: Device) {
|
||||||
top[name] = device
|
top[name] = device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package hep.dataforge.control.controllers
|
package hep.dataforge.control.controllers
|
||||||
|
|
||||||
import hep.dataforge.control.api.Consumer
|
|
||||||
import hep.dataforge.control.api.DeviceHub
|
import hep.dataforge.control.api.DeviceHub
|
||||||
import hep.dataforge.control.api.DeviceListener
|
import hep.dataforge.control.api.DeviceListener
|
||||||
import hep.dataforge.control.api.get
|
import hep.dataforge.control.api.get
|
||||||
|
import hep.dataforge.io.Consumer
|
||||||
import hep.dataforge.io.Envelope
|
import hep.dataforge.io.Envelope
|
||||||
import hep.dataforge.io.Responder
|
import hep.dataforge.io.Responder
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
@ -11,6 +11,7 @@ import hep.dataforge.meta.get
|
|||||||
import hep.dataforge.meta.string
|
import hep.dataforge.meta.string
|
||||||
import hep.dataforge.meta.wrap
|
import hep.dataforge.meta.wrap
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
@ -38,7 +39,7 @@ class HubController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val listeners: Map<Name, DeviceListener> = hub.devices.mapValues { (name, device) ->
|
private val listeners: Map<NameToken, DeviceListener> = hub.devices.mapValues { (name, device) ->
|
||||||
object : DeviceListener {
|
object : DeviceListener {
|
||||||
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
|
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
|
||||||
if (value == null) return
|
if (value == null) return
|
||||||
@ -62,7 +63,7 @@ class HubController(
|
|||||||
|
|
||||||
suspend fun respondMessage(message: DeviceMessage): DeviceMessage = try {
|
suspend fun respondMessage(message: DeviceMessage): DeviceMessage = try {
|
||||||
val targetName = message.target?.toName() ?: Name.EMPTY
|
val targetName = message.target?.toName() ?: Name.EMPTY
|
||||||
val device = hub[targetName]
|
val device = hub[targetName] ?: error("The device with name $targetName not found in $hub")
|
||||||
DeviceController.respondMessage(device, targetName.toString(), message)
|
DeviceController.respondMessage(device, targetName.toString(), message)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
DeviceMessage.fail {
|
DeviceMessage.fail {
|
||||||
@ -72,7 +73,7 @@ class HubController(
|
|||||||
|
|
||||||
override suspend fun respond(request: Envelope): Envelope = try {
|
override suspend fun respond(request: Envelope): Envelope = try {
|
||||||
val targetName = request.meta[DeviceMessage.TARGET_KEY].string?.toName() ?: Name.EMPTY
|
val targetName = request.meta[DeviceMessage.TARGET_KEY].string?.toName() ?: Name.EMPTY
|
||||||
val device = hub[targetName]
|
val device = hub[targetName] ?: error("The device with name $targetName not found in $hub")
|
||||||
if (request.data == null) {
|
if (request.data == null) {
|
||||||
DeviceController.respondMessage(device, targetName.toString(), DeviceMessage.wrap(request.meta)).wrap()
|
DeviceController.respondMessage(device, targetName.toString(), DeviceMessage.wrap(request.meta)).wrap()
|
||||||
} else {
|
} else {
|
||||||
|
@ -115,7 +115,7 @@ public fun Application.deviceModule(
|
|||||||
+"Device server dashboard"
|
+"Device server dashboard"
|
||||||
}
|
}
|
||||||
deviceNames.forEach { deviceName ->
|
deviceNames.forEach { deviceName ->
|
||||||
val device = manager[deviceName]
|
val device = manager[deviceName] ?: error("The device with name $deviceName not found in $manager")
|
||||||
div {
|
div {
|
||||||
id = deviceName
|
id = deviceName
|
||||||
h2 { +deviceName }
|
h2 { +deviceName }
|
||||||
|
@ -25,7 +25,7 @@ class DemoDevice(parentScope: CoroutineScope) : DeviceBase() {
|
|||||||
parentScope.coroutineContext + executor.asCoroutineDispatcher() + Job(parentScope.coroutineContext[Job])
|
parentScope.coroutineContext + executor.asCoroutineDispatcher() + Job(parentScope.coroutineContext[Job])
|
||||||
)
|
)
|
||||||
|
|
||||||
val timeScale: IsolatedDeviceProperty by writingVirtual(5000.0.asValue())
|
val timeScale: DeviceProperty by writingVirtual(5000.0.asValue())
|
||||||
var timeScaleValue by timeScale.double()
|
var timeScaleValue by timeScale.double()
|
||||||
|
|
||||||
val sinScale by writingVirtual(1.0.asValue())
|
val sinScale by writingVirtual(1.0.asValue())
|
||||||
|
@ -5,7 +5,7 @@ import hep.dataforge.control.controllers.devices
|
|||||||
import hep.dataforge.control.server.startDeviceServer
|
import hep.dataforge.control.server.startDeviceServer
|
||||||
import hep.dataforge.control.server.whenStarted
|
import hep.dataforge.control.server.whenStarted
|
||||||
import hep.dataforge.meta.double
|
import hep.dataforge.meta.double
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.NameToken
|
||||||
import io.ktor.server.engine.ApplicationEngine
|
import io.ktor.server.engine.ApplicationEngine
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -50,7 +50,7 @@ suspend fun Trace.updateXYFrom(flow: Flow<Iterable<Pair<Double, Double>>>) {
|
|||||||
|
|
||||||
|
|
||||||
fun startDemoDeviceServer(context: Context, device: DemoDevice): ApplicationEngine {
|
fun startDemoDeviceServer(context: Context, device: DemoDevice): ApplicationEngine {
|
||||||
context.devices.registerDevice("demo".asName(), device)
|
context.devices.registerDevice(NameToken("demo"), device)
|
||||||
val server = context.startDeviceServer(context.devices)
|
val server = context.startDeviceServer(context.devices)
|
||||||
server.whenStarted {
|
server.whenStarted {
|
||||||
plotlyModule("plots").apply {
|
plotlyModule("plots").apply {
|
||||||
|
@ -5,7 +5,6 @@ plugins {
|
|||||||
|
|
||||||
//TODO to be moved to a separate project
|
//TODO to be moved to a separate project
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":dataforge-device-core"))
|
implementation(project(":dataforge-device-core"))
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
package ru.mipt.npm.devices.pimotionmaster
|
package ru.mipt.npm.devices.pimotionmaster
|
||||||
|
|
||||||
import hep.dataforge.control.base.DeviceBase
|
import hep.dataforge.control.base.*
|
||||||
import hep.dataforge.control.base.DeviceProperty
|
|
||||||
import hep.dataforge.control.base.writingVirtual
|
|
||||||
import hep.dataforge.control.ports.Port
|
import hep.dataforge.control.ports.Port
|
||||||
import hep.dataforge.control.ports.PortProxy
|
import hep.dataforge.control.ports.PortProxy
|
||||||
|
import hep.dataforge.control.ports.send
|
||||||
import hep.dataforge.control.ports.withDelimiter
|
import hep.dataforge.control.ports.withDelimiter
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.values.Null
|
import hep.dataforge.values.Null
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
|
import kotlinx.coroutines.flow.toList
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
public class PiMotionMasterDevice(
|
||||||
|
parentScope: CoroutineScope,
|
||||||
|
private val portFactory: suspend (MetaItem<*>?) -> Port,
|
||||||
|
) : DeviceBase() {
|
||||||
|
|
||||||
class PiMotionMasterDevice(parentScope: CoroutineScope, val portFactory: suspend (MetaItem<*>?) -> Port) : DeviceBase() {
|
|
||||||
override val scope: CoroutineScope = CoroutineScope(
|
override val scope: CoroutineScope = CoroutineScope(
|
||||||
parentScope.coroutineContext + Job(parentScope.coroutineContext[Job])
|
parentScope.coroutineContext + Job(parentScope.coroutineContext[Job])
|
||||||
)
|
)
|
||||||
@ -23,14 +30,81 @@ class PiMotionMasterDevice(parentScope: CoroutineScope, val portFactory: suspend
|
|||||||
|
|
||||||
private val connector = PortProxy { portFactory(port.value) }
|
private val connector = PortProxy { portFactory(port.value) }
|
||||||
|
|
||||||
private suspend fun readPhrase(command: String) {
|
private val mutex = Mutex()
|
||||||
connector.receiving().withDelimiter("\n").first { it.startsWith(command) }
|
|
||||||
|
private suspend fun sendCommand(command: String, vararg arguments: String) {
|
||||||
|
val joinedArguments = if (arguments.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
arguments.joinToString(prefix = " ", separator = " ", postfix = "")
|
||||||
|
}
|
||||||
|
val stringToSend = "$command$joinedArguments\n"
|
||||||
|
connector.send(stringToSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
/**
|
||||||
// val firmwareVersion by reading {
|
* Send a synchronous request and receive a list of lines as a response
|
||||||
// connector.r
|
*/
|
||||||
// }
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requestAndParse(command: String, vararg arguments: String): Map<String, String> = buildMap {
|
||||||
|
request(command, *arguments).forEach { line ->
|
||||||
|
val (key, value) = line.split("=")
|
||||||
|
put(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a synchronous command
|
||||||
|
*/
|
||||||
|
private suspend fun send(command: String, vararg arguments: String) {
|
||||||
|
mutex.withLock {
|
||||||
|
sendCommand(command, *arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public val initialize: Action by action {
|
||||||
|
send("INI")
|
||||||
|
}
|
||||||
|
|
||||||
|
public val firmwareVersion: ReadOnlyDeviceProperty by readingString {
|
||||||
|
request("VER?").first()
|
||||||
|
}
|
||||||
|
|
||||||
|
public inner class Axis(public val axisId: String) : DeviceBase() {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
setter = { oldValue, newValue ->
|
||||||
|
val value = if(newValue){
|
||||||
|
"1"
|
||||||
|
} else {
|
||||||
|
"0"
|
||||||
|
}
|
||||||
|
send("EAX", axisId, value)
|
||||||
|
oldValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
public val halt: Action by action {
|
||||||
|
send("HLT", axisId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
//list everything here to ensure it is initialized
|
||||||
|
initialize
|
||||||
|
firmwareVersion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user