Remove stand-alone properties and use specs instead
This commit is contained in:
parent
1b021ca5ca
commit
ed2a2a29af
@ -57,7 +57,7 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A subscription-based [Flow] of [DeviceMessage] provided by device. The flow is guaranteed to be readable
|
* A subscription-based [Flow] of [DeviceMessage] provided by device. The flow is guaranteed to be readable
|
||||||
* multiple times
|
* multiple times.
|
||||||
*/
|
*/
|
||||||
public val messageFlow: Flow<DeviceMessage>
|
public val messageFlow: Flow<DeviceMessage>
|
||||||
|
|
||||||
@ -67,6 +67,14 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
|||||||
*/
|
*/
|
||||||
public suspend fun execute(action: String, argument: Meta? = null): Meta?
|
public suspend fun execute(action: String, argument: Meta? = null): Meta?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the device. This function suspends until the device is finished initialization
|
||||||
|
*/
|
||||||
|
public suspend fun open(): Unit = Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close and terminate the device. This function does not wait for device to be closed.
|
||||||
|
*/
|
||||||
override fun close() {
|
override fun close() {
|
||||||
cancel("The device is closed")
|
cancel("The device is closed")
|
||||||
}
|
}
|
||||||
@ -85,7 +93,7 @@ public suspend fun Device.getOrReadProperty(propertyName: String): Meta =
|
|||||||
/**
|
/**
|
||||||
* Get a snapshot of logical state of the device
|
* Get a snapshot of logical state of the device
|
||||||
*
|
*
|
||||||
* TODO currently this
|
* TODO currently this
|
||||||
*/
|
*/
|
||||||
public fun Device.getProperties(): Meta = Meta {
|
public fun Device.getProperties(): Meta = Meta {
|
||||||
for (descriptor in propertyDescriptors) {
|
for (descriptor in propertyDescriptors) {
|
||||||
@ -97,4 +105,4 @@ public fun Device.getProperties(): Meta = Meta {
|
|||||||
* Subscribe on property changes for the whole device
|
* Subscribe on property changes for the whole device
|
||||||
*/
|
*/
|
||||||
public fun Device.onPropertyChange(callback: suspend PropertyChangedMessage.() -> Unit): Job =
|
public fun Device.onPropertyChange(callback: suspend PropertyChangedMessage.() -> Unit): Job =
|
||||||
messageFlow.filterIsInstance<PropertyChangedMessage>().onEach(callback).launchIn(this)
|
messageFlow.filterIsInstance<PropertyChangedMessage>().onEach(callback).launchIn(this)
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.base
|
|
||||||
|
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
|
|
||||||
public interface DeviceAction {
|
|
||||||
public val name: String
|
|
||||||
public val descriptor: ActionDescriptor
|
|
||||||
public suspend operator fun invoke(arg: Meta? = null): Meta?
|
|
||||||
}
|
|
@ -1,252 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.base
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import ru.mipt.npm.controls.api.*
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import kotlin.collections.set
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
//TODO move to DataForge-core
|
|
||||||
@DFExperimental
|
|
||||||
public data class LogEntry(val content: String, val priority: Int = 0)
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
private open class BasicReadOnlyDeviceProperty(
|
|
||||||
val device: DeviceBase,
|
|
||||||
override val name: String,
|
|
||||||
default: Meta?,
|
|
||||||
override val descriptor: PropertyDescriptor,
|
|
||||||
private val getter: suspend (before: Meta?) -> Meta,
|
|
||||||
) : ReadOnlyDeviceProperty {
|
|
||||||
|
|
||||||
override val scope: CoroutineScope get() = device
|
|
||||||
|
|
||||||
private val state: MutableStateFlow<Meta?> = MutableStateFlow(default)
|
|
||||||
override val value: Meta? get() = state.value
|
|
||||||
|
|
||||||
override suspend fun invalidate() {
|
|
||||||
state.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateLogical(item: Meta) {
|
|
||||||
state.value = item
|
|
||||||
scope.launch {
|
|
||||||
device.sharedMessageFlow.emit(
|
|
||||||
PropertyChangedMessage(
|
|
||||||
property = name,
|
|
||||||
value = item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun read(force: Boolean): Meta {
|
|
||||||
//backup current value
|
|
||||||
val currentValue = value
|
|
||||||
return if (force || currentValue == null) {
|
|
||||||
//all device operations should be run on device context
|
|
||||||
//propagate error, but do not fail scope
|
|
||||||
val res = withContext(scope.coroutineContext + SupervisorJob(scope.coroutineContext[Job])) {
|
|
||||||
getter(currentValue)
|
|
||||||
}
|
|
||||||
updateLogical(res)
|
|
||||||
res
|
|
||||||
} else {
|
|
||||||
currentValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun flow(): StateFlow<Meta?> = state
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
private class BasicDeviceProperty(
|
|
||||||
device: DeviceBase,
|
|
||||||
name: String,
|
|
||||||
default: Meta?,
|
|
||||||
descriptor: PropertyDescriptor,
|
|
||||||
getter: suspend (Meta?) -> Meta,
|
|
||||||
private val setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
|
||||||
) : BasicReadOnlyDeviceProperty(device, name, default, descriptor, getter), DeviceProperty {
|
|
||||||
|
|
||||||
override var value: Meta?
|
|
||||||
get() = super.value
|
|
||||||
set(value) {
|
|
||||||
scope.launch {
|
|
||||||
if (value == null) {
|
|
||||||
invalidate()
|
|
||||||
} else {
|
|
||||||
write(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val writeLock = Mutex()
|
|
||||||
|
|
||||||
override suspend fun write(item: Meta) {
|
|
||||||
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 + SupervisorJob(scope.coroutineContext[Job])) {
|
|
||||||
setter(oldValue, item)?.let {
|
|
||||||
updateLogical(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Baseline implementation of [Device] interface
|
|
||||||
*/
|
|
||||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
|
||||||
public abstract class DeviceBase(final override val context: Context) : Device {
|
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext =
|
|
||||||
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
|
||||||
|
|
||||||
private val _properties = HashMap<String, ReadOnlyDeviceProperty>()
|
|
||||||
public val properties: Map<String, ReadOnlyDeviceProperty> get() = _properties
|
|
||||||
private val _actions = HashMap<String, DeviceAction>()
|
|
||||||
public val actions: Map<String, DeviceAction> get() = _actions
|
|
||||||
|
|
||||||
internal val sharedMessageFlow = MutableSharedFlow<DeviceMessage>()
|
|
||||||
|
|
||||||
override val messageFlow: SharedFlow<DeviceMessage> get() = sharedMessageFlow
|
|
||||||
private val sharedLogFlow = MutableSharedFlow<LogEntry>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The [SharedFlow] of log messages
|
|
||||||
*/
|
|
||||||
@DFExperimental
|
|
||||||
public val logFlow: SharedFlow<LogEntry>
|
|
||||||
get() = sharedLogFlow
|
|
||||||
|
|
||||||
protected suspend fun log(message: String, priority: Int = 0) {
|
|
||||||
sharedLogFlow.emit(LogEntry(message, priority))
|
|
||||||
}
|
|
||||||
|
|
||||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
|
||||||
get() = _properties.values.map { it.descriptor }
|
|
||||||
|
|
||||||
override val actionDescriptors: Collection<ActionDescriptor>
|
|
||||||
get() = _actions.values.map { it.descriptor }
|
|
||||||
|
|
||||||
private fun <P : ReadOnlyDeviceProperty> registerProperty(name: String, property: P) {
|
|
||||||
if (_properties.contains(name)) error("Property with name $name already registered")
|
|
||||||
_properties[name] = property
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun registerAction(name: String, action: DeviceAction) {
|
|
||||||
if (_actions.contains(name)) error("Action with name $name already registered")
|
|
||||||
_actions[name] = action
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun readProperty(propertyName: String): Meta =
|
|
||||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).read()
|
|
||||||
|
|
||||||
override fun getProperty(propertyName: String): Meta? =
|
|
||||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).value
|
|
||||||
|
|
||||||
override suspend fun invalidate(propertyName: String) {
|
|
||||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun writeProperty(propertyName: String, value: Meta) {
|
|
||||||
(_properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write(
|
|
||||||
value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun execute(action: String, argument: Meta?): Meta? =
|
|
||||||
(_actions[action] ?: error("Request with name $action not defined")).invoke(argument)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a bound read-only property with given [getter]
|
|
||||||
*/
|
|
||||||
public fun createReadOnlyProperty(
|
|
||||||
name: String,
|
|
||||||
default: Meta?,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend (Meta?) -> Meta,
|
|
||||||
): ReadOnlyDeviceProperty {
|
|
||||||
val property = BasicReadOnlyDeviceProperty(
|
|
||||||
this,
|
|
||||||
name,
|
|
||||||
default,
|
|
||||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
|
||||||
getter
|
|
||||||
)
|
|
||||||
registerProperty(name, property)
|
|
||||||
return property
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a bound mutable property with given [getter] and [setter]
|
|
||||||
*/
|
|
||||||
internal fun createMutableProperty(
|
|
||||||
name: String,
|
|
||||||
default: Meta?,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend (Meta?) -> Meta,
|
|
||||||
setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
|
||||||
): DeviceProperty {
|
|
||||||
val property = BasicDeviceProperty(
|
|
||||||
this,
|
|
||||||
name,
|
|
||||||
default,
|
|
||||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
|
||||||
getter,
|
|
||||||
setter
|
|
||||||
)
|
|
||||||
registerProperty(name, property)
|
|
||||||
return property
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A stand-alone action
|
|
||||||
*/
|
|
||||||
private inner class BasicDeviceAction(
|
|
||||||
override val name: String,
|
|
||||||
override val descriptor: ActionDescriptor,
|
|
||||||
private val block: suspend (Meta?) -> Meta?,
|
|
||||||
) : DeviceAction {
|
|
||||||
override suspend fun invoke(arg: Meta?): Meta? =
|
|
||||||
withContext(coroutineContext) {
|
|
||||||
block(arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new bound action
|
|
||||||
*/
|
|
||||||
internal fun createAction(
|
|
||||||
name: String,
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
block: suspend (Meta?) -> Meta?,
|
|
||||||
): DeviceAction {
|
|
||||||
val action = BasicDeviceAction(name, ActionDescriptor(name).apply(descriptorBuilder), block)
|
|
||||||
registerAction(name, action)
|
|
||||||
return action
|
|
||||||
}
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.base
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read-only device property
|
|
||||||
*/
|
|
||||||
public interface ReadOnlyDeviceProperty {
|
|
||||||
/**
|
|
||||||
* Property name, should be unique in device
|
|
||||||
*/
|
|
||||||
public val name: String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Property descriptor
|
|
||||||
*/
|
|
||||||
public val descriptor: PropertyDescriptor
|
|
||||||
|
|
||||||
public val scope: CoroutineScope
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erase logical value and force re-read from device on next [read]
|
|
||||||
*/
|
|
||||||
public suspend fun invalidate()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Directly update property logical value and notify listener without writing it to device
|
|
||||||
*/
|
|
||||||
public fun updateLogical(item: Meta)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get cached value and return null if value is invalid or not initialized
|
|
||||||
*/
|
|
||||||
public val value: Meta?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read value either from cache if cache is valid or directly from physical device.
|
|
||||||
* If [force], reread from physical state even if the logical state is set.
|
|
||||||
*/
|
|
||||||
public suspend fun read(force: Boolean = false): Meta
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The [Flow] representing future logical states of the property.
|
|
||||||
* Produces null when the state is invalidated
|
|
||||||
*/
|
|
||||||
public fun flow(): Flow<Meta?>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Launch recurring force re-read job on a property scope with given [duration] between reads.
|
|
||||||
*/
|
|
||||||
public fun ReadOnlyDeviceProperty.readEvery(duration: Duration): Job = scope.launch {
|
|
||||||
while (isActive) {
|
|
||||||
read(true)
|
|
||||||
delay(duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A writeable device property with non-suspended write
|
|
||||||
*/
|
|
||||||
public interface DeviceProperty : ReadOnlyDeviceProperty {
|
|
||||||
override var value: Meta?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write value to physical device. Invalidates logical value, but does not update it automatically
|
|
||||||
*/
|
|
||||||
public suspend fun write(item: Meta)
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.base
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.objectToMeta(obj))
|
|
||||||
}
|
|
||||||
|
|
||||||
public open val typedValue: T? get() = value?.let { converter.metaToObject(it) }
|
|
||||||
|
|
||||||
public suspend fun readTyped(force: Boolean = false): T {
|
|
||||||
val meta = read(force)
|
|
||||||
return converter.metaToObject(meta)
|
|
||||||
?: error("Meta $meta could not be converted by $converter")
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun flowTyped(): Flow<T?> = flow().map { it?.let { converter.metaToObject(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: Meta?
|
|
||||||
get() = property.value
|
|
||||||
set(arg) {
|
|
||||||
property.value = arg
|
|
||||||
}
|
|
||||||
|
|
||||||
public override var typedValue: T?
|
|
||||||
get() = value?.let { converter.metaToObject(it) }
|
|
||||||
set(arg) {
|
|
||||||
property.value = arg?.let { converter.objectToMeta(arg) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun write(item: Meta) {
|
|
||||||
property.write(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun write(obj: T) {
|
|
||||||
property.write(converter.objectToMeta(obj))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.base
|
|
||||||
|
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MutableMeta
|
|
||||||
import space.kscience.dataforge.values.Value
|
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
|
|
||||||
private fun <D : DeviceBase> D.provideAction(): ReadOnlyProperty<D, DeviceAction> =
|
|
||||||
ReadOnlyProperty { _: D, property: KProperty<*> ->
|
|
||||||
val name = property.name
|
|
||||||
return@ReadOnlyProperty actions[name]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
public typealias ActionDelegate = ReadOnlyProperty<DeviceBase, DeviceAction>
|
|
||||||
|
|
||||||
private class ActionProvider<D : DeviceBase>(
|
|
||||||
val owner: D,
|
|
||||||
val descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
val block: suspend (Meta?) -> Meta?,
|
|
||||||
) : PropertyDelegateProvider<D, ActionDelegate> {
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ActionDelegate {
|
|
||||||
val name = property.name
|
|
||||||
owner.createAction(name, descriptorBuilder, block)
|
|
||||||
return owner.provideAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun DeviceBase.requesting(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
action: suspend (Meta?) -> Meta?,
|
|
||||||
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder, action)
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.requestingValue(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
action: suspend (Meta?) -> Any?,
|
|
||||||
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
|
||||||
val res = action(it)
|
|
||||||
Meta(Value.of(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.requestingMeta(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
action: suspend MutableMeta.(Meta?) -> Unit,
|
|
||||||
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
|
||||||
Meta { action(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun DeviceBase.acting(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
action: suspend (Meta?) -> Unit,
|
|
||||||
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
|
||||||
action(it)
|
|
||||||
null
|
|
||||||
}
|
|
@ -1,283 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.base
|
|
||||||
|
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MutableMeta
|
|
||||||
import space.kscience.dataforge.meta.boolean
|
|
||||||
import space.kscience.dataforge.meta.double
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
import space.kscience.dataforge.values.Null
|
|
||||||
import space.kscience.dataforge.values.Value
|
|
||||||
import space.kscience.dataforge.values.asValue
|
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
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,
|
|
||||||
val default: Meta?,
|
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
private val getter: suspend (Meta?) -> Meta,
|
|
||||||
) : PropertyDelegateProvider<D, ReadOnlyPropertyDelegate> {
|
|
||||||
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate {
|
|
||||||
val name = property.name
|
|
||||||
owner.createReadOnlyProperty(name, default, descriptorBuilder, getter)
|
|
||||||
return owner.provideProperty(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TypedReadOnlyDevicePropertyProvider<D : DeviceBase, T : Any>(
|
|
||||||
val owner: D,
|
|
||||||
val default: Meta?,
|
|
||||||
val converter: MetaConverter<T>,
|
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
private val getter: suspend (Meta?) -> Meta,
|
|
||||||
) : PropertyDelegateProvider<D, TypedReadOnlyPropertyDelegate<T>> {
|
|
||||||
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedReadOnlyPropertyDelegate<T> {
|
|
||||||
val name = property.name
|
|
||||||
owner.createReadOnlyProperty(name, default, descriptorBuilder, getter)
|
|
||||||
return owner.provideProperty(name, converter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun DeviceBase.reading(
|
|
||||||
default: Meta? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend (Meta?) -> Meta,
|
|
||||||
): 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 { Meta(it) },
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = { Meta(Value.of(getter())) }
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun DeviceBase.readingNumber(
|
|
||||||
default: Number? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend () -> Number,
|
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Number>> = TypedReadOnlyDevicePropertyProvider(
|
|
||||||
this,
|
|
||||||
default?.let { Meta(it.asValue()) },
|
|
||||||
MetaConverter.number,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = {
|
|
||||||
val number = getter()
|
|
||||||
Meta(number.asValue())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun DeviceBase.readingDouble(
|
|
||||||
default: Number? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend () -> Double,
|
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Double>> = TypedReadOnlyDevicePropertyProvider(
|
|
||||||
this,
|
|
||||||
default?.let { Meta(it.asValue()) },
|
|
||||||
MetaConverter.double,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = {
|
|
||||||
val number = getter()
|
|
||||||
Meta(number.asValue())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun DeviceBase.readingString(
|
|
||||||
default: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend () -> String,
|
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<String>> = TypedReadOnlyDevicePropertyProvider(
|
|
||||||
this,
|
|
||||||
default?.let { Meta(it.asValue()) },
|
|
||||||
MetaConverter.string,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = {
|
|
||||||
val number = getter()
|
|
||||||
Meta(number.asValue())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun DeviceBase.readingBoolean(
|
|
||||||
default: Boolean? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend () -> Boolean,
|
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Boolean>> = TypedReadOnlyDevicePropertyProvider(
|
|
||||||
this,
|
|
||||||
default?.let { Meta(it.asValue()) },
|
|
||||||
MetaConverter.boolean,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = {
|
|
||||||
val boolean = getter()
|
|
||||||
Meta(boolean.asValue())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun DeviceBase.readingMeta(
|
|
||||||
default: Meta? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend MutableMeta.() -> Unit,
|
|
||||||
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Meta>> = TypedReadOnlyDevicePropertyProvider(
|
|
||||||
this,
|
|
||||||
default,
|
|
||||||
MetaConverter.meta,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = {
|
|
||||||
Meta { getter() }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
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,
|
|
||||||
val default: Meta?,
|
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
private val getter: suspend (Meta?) -> Meta,
|
|
||||||
private val setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
|
||||||
) : PropertyDelegateProvider<D, PropertyDelegate> {
|
|
||||||
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate {
|
|
||||||
val name = property.name
|
|
||||||
owner.createMutableProperty(name, default, descriptorBuilder, getter, setter)
|
|
||||||
return owner.provideMutableProperty(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TypedDevicePropertyProvider<D : DeviceBase, T : Any>(
|
|
||||||
val owner: D,
|
|
||||||
val default: Meta?,
|
|
||||||
val converter: MetaConverter<T>,
|
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
private val getter: suspend (Meta?) -> Meta,
|
|
||||||
private val setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
|
||||||
) : PropertyDelegateProvider<D, TypedPropertyDelegate<T>> {
|
|
||||||
|
|
||||||
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedPropertyDelegate<T> {
|
|
||||||
val name = property.name
|
|
||||||
owner.createMutableProperty(name, default, descriptorBuilder, getter, setter)
|
|
||||||
return owner.provideMutableProperty(name, converter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun DeviceBase.writing(
|
|
||||||
default: Meta? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend (Meta?) -> Meta,
|
|
||||||
setter: suspend (oldValue: Meta?, newValue: Meta) -> Meta?,
|
|
||||||
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = DevicePropertyProvider(
|
|
||||||
this,
|
|
||||||
default,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter,
|
|
||||||
setter
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun DeviceBase.writingVirtual(
|
|
||||||
default: Meta,
|
|
||||||
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(
|
|
||||||
Meta(default),
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = { it ?: Meta(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, TypedPropertyDelegate<Double>> {
|
|
||||||
val innerGetter: suspend (Meta?) -> Meta = {
|
|
||||||
Meta(getter(it.double ?: Double.NaN).asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
val innerSetter: suspend (oldValue: Meta?, newValue: Meta) -> Meta? = { oldValue, newValue ->
|
|
||||||
setter(oldValue.double, newValue.double ?: Double.NaN)?.asMeta()
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypedDevicePropertyProvider(
|
|
||||||
this,
|
|
||||||
Meta(Double.NaN.asValue()),
|
|
||||||
MetaConverter.double,
|
|
||||||
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, TypedPropertyDelegate<Boolean>> {
|
|
||||||
val innerGetter: suspend (Meta?) -> Meta = {
|
|
||||||
Meta(getter(it.boolean).asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
val innerSetter: suspend (oldValue: Meta?, newValue: Meta) -> Meta? = { oldValue, newValue ->
|
|
||||||
setter(oldValue.boolean, newValue.boolean ?: error("Can't convert $newValue to boolean"))?.asValue()
|
|
||||||
?.let { Meta(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypedDevicePropertyProvider(
|
|
||||||
this,
|
|
||||||
Meta(Null),
|
|
||||||
MetaConverter.boolean,
|
|
||||||
descriptorBuilder,
|
|
||||||
innerGetter,
|
|
||||||
innerSetter
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
package ru.mipt.npm.controls.spec
|
||||||
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
@ -13,24 +13,15 @@ import space.kscience.dataforge.context.Global
|
|||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
/**
|
|
||||||
* A device generated from specification
|
|
||||||
* @param D recursive self-type for properties and actions
|
|
||||||
*/
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||||
public val spec: DeviceSpec<D>,
|
override val context: Context = Global,
|
||||||
context: Context = Global,
|
public val meta: Meta = Meta.EMPTY
|
||||||
meta: Meta = Meta.EMPTY
|
|
||||||
) : Device {
|
) : Device {
|
||||||
override var context: Context = context
|
|
||||||
internal set
|
|
||||||
|
|
||||||
public var meta: Meta = meta
|
public abstract val properties: Map<String, DevicePropertySpec<D, *>> //get() = spec.properties
|
||||||
internal set
|
public abstract val actions: Map<String, DeviceActionSpec<D, *, *>> //get() = spec.actions
|
||||||
|
|
||||||
public val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
|
||||||
public val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
|
||||||
|
|
||||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
override val propertyDescriptors: Collection<PropertyDescriptor>
|
||||||
get() = properties.values.map { it.descriptor }
|
get() = properties.values.map { it.descriptor }
|
||||||
@ -68,6 +59,13 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update logical state using given [spec] and its convertor
|
||||||
|
*/
|
||||||
|
protected suspend fun <T> updateLogical(spec: DevicePropertySpec<D, T>, value: T) {
|
||||||
|
updateLogical(spec.name, spec.converter.objectToMeta(value))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force read physical value and push an update if it is changed. It does not matter if logical state is present.
|
* Force read physical value and push an update if it is changed. It does not matter if logical state is present.
|
||||||
* The logical state is updated after read
|
* The logical state is updated after read
|
||||||
@ -98,7 +96,7 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(action: String, argument: Meta?): Meta? =
|
override suspend fun execute(action: String, argument: Meta?): Meta? =
|
||||||
actions[action]?.executeMeta(self, argument)
|
actions[action]?.executeWithMeta(self, argument)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read typed value and update/push event if needed
|
* Read typed value and update/push event if needed
|
||||||
@ -123,19 +121,20 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
public suspend operator fun <I, O> DeviceActionSpec<D, I, O>.invoke(input: I? = null): O? = execute(self, input)
|
||||||
with(spec) { self.onShutdown() }
|
|
||||||
super.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public suspend fun <D : DeviceBySpec<D>, T : Any> D.read(
|
/**
|
||||||
propertySpec: DevicePropertySpec<D, T>
|
* A device generated from specification
|
||||||
): T = propertySpec.read()
|
* @param D recursive self-type for properties and actions
|
||||||
|
*/
|
||||||
public fun <D : DeviceBySpec<D>, T> D.write(
|
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
public val spec: DeviceSpec<D>,
|
||||||
value: T
|
context: Context = Global,
|
||||||
): Job = launch {
|
meta: Meta = Meta.EMPTY
|
||||||
propertySpec.write(value)
|
) : DeviceBase<D>(context, meta) {
|
||||||
|
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
||||||
|
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,14 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
package ru.mipt.npm.controls.spec
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
import ru.mipt.npm.controls.api.ActionDescriptor
|
||||||
import ru.mipt.npm.controls.api.Device
|
import ru.mipt.npm.controls.api.Device
|
||||||
|
import ru.mipt.npm.controls.api.PropertyChangedMessage
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
@ -75,11 +82,48 @@ public interface DeviceActionSpec<in D : Device, I, O> {
|
|||||||
public suspend fun execute(device: D, input: I?): O?
|
public suspend fun execute(device: D, input: I?): O?
|
||||||
}
|
}
|
||||||
|
|
||||||
public suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeMeta(
|
public suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
|
||||||
device: D,
|
device: D,
|
||||||
item: Meta?
|
item: Meta?
|
||||||
): Meta? {
|
): Meta? {
|
||||||
val arg = item?.let { inputConverter.metaToObject(item) }
|
val arg = item?.let { inputConverter.metaToObject(item) }
|
||||||
val res = execute(device, arg)
|
val res = execute(device, arg)
|
||||||
return res?.let { outputConverter.objectToMeta(res) }
|
return res?.let { outputConverter.objectToMeta(res) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public suspend fun <D : DeviceBase<D>, T : Any> D.read(
|
||||||
|
propertySpec: DevicePropertySpec<D, T>
|
||||||
|
): T = propertySpec.read()
|
||||||
|
|
||||||
|
public suspend fun <D : Device, T : Any> D.read(
|
||||||
|
propertySpec: DevicePropertySpec<D, T>
|
||||||
|
): T = propertySpec.converter.metaToObject(readProperty(propertySpec.name))
|
||||||
|
?: error("Property meta converter returned null")
|
||||||
|
|
||||||
|
public fun <D : Device, T> D.write(
|
||||||
|
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||||
|
value: T
|
||||||
|
): Job = launch {
|
||||||
|
writeProperty(propertySpec.name, propertySpec.converter.objectToMeta(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <D : DeviceBase<D>, T> D.write(
|
||||||
|
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||||
|
value: T
|
||||||
|
): Job = launch {
|
||||||
|
propertySpec.write(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type safe property change listener
|
||||||
|
*/
|
||||||
|
public fun <D : Device, T> Device.onPropertyChange(
|
||||||
|
spec: DevicePropertySpec<D, T>,
|
||||||
|
callback: suspend PropertyChangedMessage.(T?) -> Unit
|
||||||
|
): Job = messageFlow
|
||||||
|
.filterIsInstance<PropertyChangedMessage>()
|
||||||
|
.filter { it.property == spec.name }
|
||||||
|
.onEach { change ->
|
||||||
|
change.callback(spec.converter.metaToObject(change.value))
|
||||||
|
}.launchIn(this)
|
@ -1,10 +1,9 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
package ru.mipt.npm.controls.spec
|
||||||
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
import ru.mipt.npm.controls.api.ActionDescriptor
|
||||||
|
import ru.mipt.npm.controls.api.Device
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.context.Factory
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
import kotlin.properties.PropertyDelegateProvider
|
||||||
@ -14,9 +13,7 @@ import kotlin.reflect.KProperty
|
|||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
public abstract class DeviceSpec<D : Device> {
|
||||||
private val buildDevice: () -> D
|
|
||||||
) : Factory<D> {
|
|
||||||
private val _properties = HashMap<String, DevicePropertySpec<D, *>>()
|
private val _properties = HashMap<String, DevicePropertySpec<D, *>>()
|
||||||
public val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
|
public val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
|
||||||
|
|
||||||
@ -75,8 +72,8 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
|
|
||||||
public fun <T : Any> property(
|
public fun <T : Any> property(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> T
|
read: suspend D.() -> T
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
|
||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||||
@ -96,8 +93,8 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
|
|
||||||
public fun <T : Any> property(
|
public fun <T : Any> property(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> T,
|
read: suspend D.() -> T,
|
||||||
write: suspend D.(T) -> Unit
|
write: suspend D.(T) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>>> =
|
||||||
@ -129,8 +126,8 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
public fun <I : Any, O : Any> action(
|
public fun <I : Any, O : Any> action(
|
||||||
inputConverter: MetaConverter<I>,
|
inputConverter: MetaConverter<I>,
|
||||||
outputConverter: MetaConverter<O>,
|
outputConverter: MetaConverter<O>,
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
execute: suspend D.(I?) -> O?
|
execute: suspend D.(I?) -> O?
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>>> =
|
||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||||
@ -153,19 +150,35 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function is executed right after device initialization is finished
|
* An action that takes [Meta] and returns [Meta]. No conversions are done
|
||||||
*/
|
*/
|
||||||
public open fun D.onStartup() {}
|
public fun metaAction(
|
||||||
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
|
execute: suspend D.(Meta?) -> Meta?
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> = action(
|
||||||
|
MetaConverter.Companion.meta,
|
||||||
|
MetaConverter.Companion.meta,
|
||||||
|
descriptorBuilder,
|
||||||
|
name
|
||||||
|
){
|
||||||
|
execute(it)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function is executed before device is shut down
|
* An action that takes no parameters and returns no values
|
||||||
*/
|
*/
|
||||||
public open fun D.onShutdown() {}
|
public fun unitAction(
|
||||||
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
override fun invoke(meta: Meta, context: Context): D = buildDevice().apply {
|
execute: suspend D.() -> Unit
|
||||||
this.context = context
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> = action(
|
||||||
this.meta = meta
|
MetaConverter.Companion.meta,
|
||||||
onStartup()
|
MetaConverter.Companion.meta,
|
||||||
|
descriptorBuilder,
|
||||||
|
name
|
||||||
|
){
|
||||||
|
execute()
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
package ru.mipt.npm.controls.spec
|
||||||
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -14,7 +14,7 @@ import kotlin.time.Duration
|
|||||||
*
|
*
|
||||||
* The flow is canceled when the device scope is canceled
|
* The flow is canceled when the device scope is canceled
|
||||||
*/
|
*/
|
||||||
public fun <D : DeviceBySpec<D>, R> D.readRecurring(interval: Duration, reader: suspend D.() -> R): Flow<R> = flow {
|
public fun <D : DeviceBase<D>, R> D.readRecurring(interval: Duration, reader: suspend D.() -> R): Flow<R> = flow {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
kotlinx.coroutines.delay(interval)
|
kotlinx.coroutines.delay(interval)
|
||||||
emit(reader())
|
emit(reader())
|
||||||
@ -24,7 +24,7 @@ public fun <D : DeviceBySpec<D>, R> D.readRecurring(interval: Duration, reader:
|
|||||||
/**
|
/**
|
||||||
* Do a recurring task on a device. The task could
|
* Do a recurring task on a device. The task could
|
||||||
*/
|
*/
|
||||||
public fun <D : DeviceBySpec<D>> D.doRecurring(interval: Duration, task: suspend D.() -> Unit): Job = launch {
|
public fun <D : DeviceBase<D>> D.doRecurring(interval: Duration, task: suspend D.() -> Unit): Job = launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
kotlinx.coroutines.delay(interval)
|
kotlinx.coroutines.delay(interval)
|
||||||
task()
|
task()
|
@ -1,4 +1,4 @@
|
|||||||
package ru.mipt.npm.controls.base
|
package ru.mipt.npm.controls.spec
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.double
|
import space.kscience.dataforge.meta.double
|
@ -1,4 +1,4 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
package ru.mipt.npm.controls.spec
|
||||||
|
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import ru.mipt.npm.controls.api.metaDescriptor
|
import ru.mipt.npm.controls.api.metaDescriptor
|
||||||
@ -10,19 +10,19 @@ import kotlin.properties.ReadOnlyProperty
|
|||||||
|
|
||||||
//read only delegates
|
//read only delegates
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.booleanProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> Boolean
|
read: suspend D.() -> Boolean
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
|
||||||
MetaConverter.boolean,
|
MetaConverter.boolean,
|
||||||
name,
|
|
||||||
{
|
{
|
||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
type(ValueType.BOOLEAN)
|
type(ValueType.BOOLEAN)
|
||||||
}
|
}
|
||||||
descriptorBuilder()
|
descriptorBuilder()
|
||||||
},
|
},
|
||||||
|
name,
|
||||||
read
|
read
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,110 +35,110 @@ private inline fun numberDescriptor(
|
|||||||
descriptorBuilder()
|
descriptorBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.numberProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
read: suspend D.() -> Number
|
read: suspend D.() -> Number
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> = property(
|
||||||
MetaConverter.number,
|
MetaConverter.number,
|
||||||
name,
|
|
||||||
numberDescriptor(descriptorBuilder),
|
numberDescriptor(descriptorBuilder),
|
||||||
|
name,
|
||||||
read
|
read
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.doubleProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> Double
|
read: suspend D.() -> Double
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> = property(
|
||||||
MetaConverter.double,
|
MetaConverter.double,
|
||||||
name,
|
|
||||||
numberDescriptor(descriptorBuilder),
|
numberDescriptor(descriptorBuilder),
|
||||||
|
name,
|
||||||
read
|
read
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.stringProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> String
|
read: suspend D.() -> String
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
|
||||||
MetaConverter.string,
|
MetaConverter.string,
|
||||||
name,
|
|
||||||
{
|
{
|
||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
type(ValueType.STRING)
|
type(ValueType.STRING)
|
||||||
}
|
}
|
||||||
descriptorBuilder()
|
descriptorBuilder()
|
||||||
},
|
},
|
||||||
|
name,
|
||||||
read
|
read
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.metaProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> Meta
|
read: suspend D.() -> Meta
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
|
||||||
MetaConverter.meta,
|
MetaConverter.meta,
|
||||||
name,
|
|
||||||
{
|
{
|
||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
type(ValueType.STRING)
|
type(ValueType.STRING)
|
||||||
}
|
}
|
||||||
descriptorBuilder()
|
descriptorBuilder()
|
||||||
},
|
},
|
||||||
|
name,
|
||||||
read
|
read
|
||||||
)
|
)
|
||||||
|
|
||||||
//read-write delegates
|
//read-write delegates
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.booleanProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> Boolean,
|
read: suspend D.() -> Boolean,
|
||||||
write: suspend D.(Boolean) -> Unit
|
write: suspend D.(Boolean) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
||||||
property(
|
property(
|
||||||
MetaConverter.boolean,
|
MetaConverter.boolean,
|
||||||
name,
|
|
||||||
{
|
{
|
||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
type(ValueType.BOOLEAN)
|
type(ValueType.BOOLEAN)
|
||||||
}
|
}
|
||||||
descriptorBuilder()
|
descriptorBuilder()
|
||||||
},
|
},
|
||||||
|
name,
|
||||||
read,
|
read,
|
||||||
write
|
write
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.numberProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> Number,
|
read: suspend D.() -> Number,
|
||||||
write: suspend D.(Number) -> Unit
|
write: suspend D.(Number) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Number>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Number>>> =
|
||||||
property(MetaConverter.number, name, numberDescriptor(descriptorBuilder), read, write)
|
property(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.doubleProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> Double,
|
read: suspend D.() -> Double,
|
||||||
write: suspend D.(Double) -> Unit
|
write: suspend D.(Double) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Double>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Double>>> =
|
||||||
property(MetaConverter.double, name, numberDescriptor(descriptorBuilder), read, write)
|
property(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.stringProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> String,
|
read: suspend D.() -> String,
|
||||||
write: suspend D.(String) -> Unit
|
write: suspend D.(String) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
||||||
property(MetaConverter.string, name, descriptorBuilder, read, write)
|
property(MetaConverter.string, descriptorBuilder, name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> DeviceSpec<D>.metaProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
||||||
name: String? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
name: String? = null,
|
||||||
read: suspend D.() -> Meta,
|
read: suspend D.() -> Meta,
|
||||||
write: suspend D.(Meta) -> Unit
|
write: suspend D.(Meta) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Meta>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Meta>>> =
|
||||||
property(MetaConverter.meta, name, descriptorBuilder, read, write)
|
property(MetaConverter.meta, descriptorBuilder, name, read, write)
|
@ -1,89 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.controllers
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import ru.mipt.npm.controls.base.*
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
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<*>): Meta =
|
|
||||||
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: Meta) {
|
|
||||||
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 { _, _ ->
|
|
||||||
runBlocking(scope.coroutineContext) {
|
|
||||||
val meta = read(forceRead)
|
|
||||||
metaConverter.metaToObject(meta)?: error("Meta $meta could not be converted by $metaConverter")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
val meta = read(forceRead)
|
|
||||||
metaConverter.metaToObject(meta)?: error("Meta $meta could not be converted by $metaConverter")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
|
||||||
this@convert.setValue(thisRef, property, value.let { metaConverter.objectToMeta(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)
|
|
@ -1,10 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blocking property get call
|
|
||||||
*/
|
|
||||||
public operator fun <D : DeviceBySpec<D>, T : Any> D.get(
|
|
||||||
propertySpec: DevicePropertySpec<D, T>
|
|
||||||
): T = runBlocking { read(propertySpec) }
|
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.mipt.npm.controls.spec
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocking property get call
|
||||||
|
*/
|
||||||
|
public operator fun <D : DeviceBase<D>, T : Any> D.get(
|
||||||
|
propertySpec: DevicePropertySpec<D, T>
|
||||||
|
): T = runBlocking { read(propertySpec) }
|
@ -4,8 +4,8 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||||
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
|
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
|
||||||
import ru.mipt.npm.controls.properties.DeviceBySpec
|
import ru.mipt.npm.controls.spec.DeviceBySpec
|
||||||
import ru.mipt.npm.controls.properties.DeviceSpec
|
import ru.mipt.npm.controls.spec.DeviceSpec
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
@ -5,7 +5,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
repositories{
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven("https://repo.kotlin.link")
|
maven("https://repo.kotlin.link")
|
||||||
@ -15,7 +15,7 @@ repositories{
|
|||||||
val ktorVersion: String by rootProject.extra
|
val ktorVersion: String by rootProject.extra
|
||||||
val rsocketVersion: String by rootProject.extra
|
val rsocketVersion: String by rootProject.extra
|
||||||
|
|
||||||
dependencies{
|
dependencies {
|
||||||
implementation(projects.controlsCore)
|
implementation(projects.controlsCore)
|
||||||
//implementation(projects.controlsServer)
|
//implementation(projects.controlsServer)
|
||||||
implementation(projects.magix.magixServer)
|
implementation(projects.magix.magixServer)
|
||||||
@ -34,15 +34,15 @@ dependencies{
|
|||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all"
|
freeCompilerArgs = freeCompilerArgs + listOf("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
javafx{
|
javafx {
|
||||||
version = "14"
|
version = "14"
|
||||||
modules("javafx.controls")
|
modules("javafx.controls")
|
||||||
}
|
}
|
||||||
|
|
||||||
application{
|
application {
|
||||||
mainClass.set("ru.mipt.npm.controls.demo.DemoControllerViewKt")
|
mainClass.set("ru.mipt.npm.controls.demo.DemoControllerViewKt")
|
||||||
}
|
}
|
@ -1,22 +1,47 @@
|
|||||||
package ru.mipt.npm.controls.demo
|
package ru.mipt.npm.controls.demo
|
||||||
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.controls.properties.*
|
import ru.mipt.npm.controls.api.metaDescriptor
|
||||||
|
import ru.mipt.npm.controls.spec.*
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
import space.kscience.dataforge.context.Factory
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.descriptors.value
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import space.kscience.dataforge.values.ValueType
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
|
|
||||||
class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<DemoDevice>(DemoDevice, context, meta) {
|
||||||
private var timeScaleState = 5000.0
|
private var timeScaleState = 5000.0
|
||||||
private var sinScaleState = 1.0
|
private var sinScaleState = 1.0
|
||||||
private var cosScaleState = 1.0
|
private var cosScaleState = 1.0
|
||||||
|
|
||||||
companion object : DeviceSpec<DemoDevice>(::DemoDevice) {
|
@OptIn(ExperimentalTime::class)
|
||||||
|
override suspend fun open() {
|
||||||
|
super.open()
|
||||||
|
launch {
|
||||||
|
sinScale.read()
|
||||||
|
cosScale.read()
|
||||||
|
timeScale.read()
|
||||||
|
}
|
||||||
|
doRecurring(Duration.milliseconds(50)) {
|
||||||
|
coordinates.read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object : DeviceSpec<DemoDevice>(), Factory<DemoDevice> {
|
||||||
// register virtual properties based on actual object state
|
// register virtual properties based on actual object state
|
||||||
val timeScale by property(MetaConverter.double, DemoDevice::timeScaleState)
|
val timeScale by property(MetaConverter.double, DemoDevice::timeScaleState) {
|
||||||
|
metaDescriptor {
|
||||||
|
type(ValueType.NUMBER)
|
||||||
|
}
|
||||||
|
info = "Real to virtual time scale"
|
||||||
|
}
|
||||||
|
|
||||||
val sinScale by property(MetaConverter.double, DemoDevice::sinScaleState)
|
val sinScale by property(MetaConverter.double, DemoDevice::sinScaleState)
|
||||||
val cosScale by property(MetaConverter.double, DemoDevice::cosScaleState)
|
val cosScale by property(MetaConverter.double, DemoDevice::cosScaleState)
|
||||||
|
|
||||||
@ -30,7 +55,13 @@ class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
|||||||
kotlin.math.cos(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
|
kotlin.math.cos(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
|
||||||
}
|
}
|
||||||
|
|
||||||
val coordinates by metaProperty {
|
val coordinates by metaProperty(
|
||||||
|
descriptorBuilder = {
|
||||||
|
metaDescriptor {
|
||||||
|
value("time", ValueType.NUMBER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
Meta {
|
Meta {
|
||||||
val time = Instant.now()
|
val time = Instant.now()
|
||||||
"time" put time.toEpochMilli()
|
"time" put time.toEpochMilli()
|
||||||
@ -46,15 +77,6 @@ class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
override fun invoke(meta: Meta, context: Context): DemoDevice = DemoDevice(context, meta)
|
||||||
override fun DemoDevice.onStartup() {
|
|
||||||
launch {
|
|
||||||
sinScale.read()
|
|
||||||
cosScale.read()
|
|
||||||
}
|
|
||||||
doRecurring(Duration.milliseconds(50)){
|
|
||||||
coordinates.read()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,6 +13,9 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.controls.controllers.DeviceManager
|
import ru.mipt.npm.controls.controllers.DeviceManager
|
||||||
import ru.mipt.npm.controls.controllers.installing
|
import ru.mipt.npm.controls.controllers.installing
|
||||||
|
import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.maxPosition
|
||||||
|
import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.minPosition
|
||||||
|
import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.position
|
||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
import space.kscience.dataforge.context.fetch
|
import space.kscience.dataforge.context.fetch
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
@ -40,28 +43,30 @@ fun VBox.piMotionMasterAxis(
|
|||||||
alignment = Pos.CENTER
|
alignment = Pos.CENTER
|
||||||
label(axisName)
|
label(axisName)
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
val min = axis.minPosition.readTyped(true)
|
with(axis) {
|
||||||
val max = axis.maxPosition.readTyped(true)
|
val min = minPosition.read()
|
||||||
val positionProperty = axis.position.fxProperty(axis)
|
val max = maxPosition.read()
|
||||||
val startPosition = axis.position.readTyped(true)
|
val positionProperty = fxProperty(position)
|
||||||
runLater {
|
val startPosition = position.read()
|
||||||
vbox {
|
runLater {
|
||||||
hgrow = Priority.ALWAYS
|
vbox {
|
||||||
slider(min..max, startPosition) {
|
hgrow = Priority.ALWAYS
|
||||||
minWidth = 300.0
|
slider(min..max, startPosition) {
|
||||||
isShowTickLabels = true
|
minWidth = 300.0
|
||||||
isShowTickMarks = true
|
isShowTickLabels = true
|
||||||
minorTickCount = 10
|
isShowTickMarks = true
|
||||||
majorTickUnit = 1.0
|
minorTickCount = 10
|
||||||
valueProperty().onChange {
|
majorTickUnit = 1.0
|
||||||
coroutineScope.launch {
|
valueProperty().onChange {
|
||||||
axis.move(value)
|
coroutineScope.launch {
|
||||||
|
axis.move(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
slider(min..max) {
|
||||||
slider(min..max) {
|
isDisable = true
|
||||||
isDisable = true
|
valueProperty().bind(positionProperty)
|
||||||
valueProperty().bind(positionProperty)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +87,7 @@ class PiMotionMasterView : View() {
|
|||||||
private val controller: PiMotionMasterController by inject()
|
private val controller: PiMotionMasterController by inject()
|
||||||
val device = controller.motionMaster
|
val device = controller.motionMaster
|
||||||
|
|
||||||
private val connectedProperty: ReadOnlyProperty<Boolean> = device.connected.fxProperty(device)
|
private val connectedProperty: ReadOnlyProperty<Boolean> = device.fxProperty(PiMotionMasterDevice.connected)
|
||||||
private val debugServerJobProperty = SimpleObjectProperty<Job>()
|
private val debugServerJobProperty = SimpleObjectProperty<Job>()
|
||||||
private val debugServerStarted = debugServerJobProperty.booleanBinding { it != null }
|
private val debugServerStarted = debugServerJobProperty.booleanBinding { it != null }
|
||||||
//private val axisList = FXCollections.observableArrayList<Map.Entry<String, PiMotionMasterDevice.Axis>>()
|
//private val axisList = FXCollections.observableArrayList<Map.Entry<String, PiMotionMasterDevice.Axis>>()
|
||||||
|
@ -13,13 +13,13 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import ru.mipt.npm.controls.api.DeviceHub
|
import ru.mipt.npm.controls.api.DeviceHub
|
||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
import ru.mipt.npm.controls.base.*
|
|
||||||
import ru.mipt.npm.controls.controllers.duration
|
|
||||||
import ru.mipt.npm.controls.ports.*
|
import ru.mipt.npm.controls.ports.*
|
||||||
|
import ru.mipt.npm.controls.spec.*
|
||||||
import space.kscience.dataforge.context.*
|
import space.kscience.dataforge.context.*
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.double
|
import space.kscience.dataforge.meta.double
|
||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import space.kscience.dataforge.names.NameToken
|
import space.kscience.dataforge.names.NameToken
|
||||||
import space.kscience.dataforge.values.asValue
|
import space.kscience.dataforge.values.asValue
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
@ -29,70 +29,20 @@ import kotlin.time.Duration
|
|||||||
class PiMotionMasterDevice(
|
class PiMotionMasterDevice(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val portFactory: PortFactory = KtorTcpPort,
|
private val portFactory: PortFactory = KtorTcpPort,
|
||||||
) : DeviceBase(context), DeviceHub {
|
) : DeviceBySpec<PiMotionMasterDevice>(PiMotionMasterDevice, context), DeviceHub {
|
||||||
|
|
||||||
private var port: Port? = null
|
private var port: Port? = null
|
||||||
//TODO make proxy work
|
//TODO make proxy work
|
||||||
//PortProxy { portFactory(address ?: error("The device is not connected"), context) }
|
//PortProxy { portFactory(address ?: error("The device is not connected"), context) }
|
||||||
|
|
||||||
|
|
||||||
val connected by readingBoolean(false, descriptorBuilder = {
|
|
||||||
info = "True if the connection address is defined and the device is initialized"
|
|
||||||
}) {
|
|
||||||
port != null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val connect: DeviceAction by acting({
|
|
||||||
info = "Connect to specific port and initialize axis"
|
|
||||||
}) { portSpec ->
|
|
||||||
//Clear current actions if present
|
|
||||||
if (port != null) {
|
|
||||||
disconnect()
|
|
||||||
}
|
|
||||||
//Update port
|
|
||||||
//address = portSpec.node
|
|
||||||
port = portFactory(portSpec ?: Meta.EMPTY, context)
|
|
||||||
connected.updateLogical(true)
|
|
||||||
// connector.open()
|
|
||||||
//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?").map { it.trim() }
|
|
||||||
if (ids != axes.keys.toList()) {
|
|
||||||
//re-define axes if needed
|
|
||||||
axes = ids.associateWith { Axis(it) }
|
|
||||||
}
|
|
||||||
Meta(ids.map { it.asValue() }.asValue())
|
|
||||||
initialize()
|
|
||||||
failIfError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val disconnect: DeviceAction by acting({
|
|
||||||
info = "Disconnect the program from the device if it is connected"
|
|
||||||
}) {
|
|
||||||
if (port != null) {
|
|
||||||
stop()
|
|
||||||
port?.close()
|
|
||||||
}
|
|
||||||
port = null
|
|
||||||
connected.updateLogical(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disconnect() {
|
fun disconnect() {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
disconnect.invoke()
|
disconnect.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val timeout: DeviceProperty by writingVirtual(200.asValue()) {
|
var timeoutValue: Duration = Duration.microseconds(200)
|
||||||
info = "Timeout"
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeoutValue: Duration by timeout.duration()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name-friendly accessor for axis
|
* Name-friendly accessor for axis
|
||||||
@ -182,166 +132,225 @@ class PiMotionMasterDevice(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val initialize: DeviceAction by acting {
|
companion object : DeviceSpec<PiMotionMasterDevice>(), Factory<PiMotionMasterDevice> {
|
||||||
send("INI")
|
|
||||||
}
|
|
||||||
|
|
||||||
val identity: ReadOnlyDeviceProperty by readingString {
|
override fun invoke(meta: Meta, context: Context): PiMotionMasterDevice = PiMotionMasterDevice(context)
|
||||||
request("*IDN?").first()
|
|
||||||
}
|
|
||||||
|
|
||||||
val firmwareVersion: ReadOnlyDeviceProperty by readingString {
|
val connected by booleanProperty(descriptorBuilder = {
|
||||||
request("VER?").first()
|
info = "True if the connection address is defined and the device is initialized"
|
||||||
}
|
}) {
|
||||||
|
port != null
|
||||||
|
}
|
||||||
|
|
||||||
val stop: DeviceAction by acting(
|
|
||||||
descriptorBuilder = {
|
val initialize by unitAction {
|
||||||
|
send("INI")
|
||||||
|
}
|
||||||
|
|
||||||
|
val identity by stringProperty {
|
||||||
|
request("*IDN?").first()
|
||||||
|
}
|
||||||
|
|
||||||
|
val firmwareVersion by stringProperty {
|
||||||
|
request("VER?").first()
|
||||||
|
}
|
||||||
|
|
||||||
|
val stop by unitAction({
|
||||||
info = "Stop all axis"
|
info = "Stop all axis"
|
||||||
},
|
}) {
|
||||||
action = { send("STP") }
|
send("STP")
|
||||||
)
|
}
|
||||||
|
|
||||||
inner class Axis(val axisId: String) : DeviceBase(context) {
|
val connect by metaAction(descriptorBuilder = {
|
||||||
|
info = "Connect to specific port and initialize axis"
|
||||||
|
}) { portSpec ->
|
||||||
|
//Clear current actions if present
|
||||||
|
if (port != null) {
|
||||||
|
disconnect()
|
||||||
|
}
|
||||||
|
//Update port
|
||||||
|
//address = portSpec.node
|
||||||
|
port = portFactory(portSpec ?: Meta.EMPTY, context)
|
||||||
|
updateLogical(connected, true)
|
||||||
|
// connector.open()
|
||||||
|
//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?").map { it.trim() }
|
||||||
|
if (ids != axes.keys.toList()) {
|
||||||
|
//re-define axes if needed
|
||||||
|
axes = ids.associateWith { Axis(this, it) }
|
||||||
|
}
|
||||||
|
Meta(ids.map { it.asValue() }.asValue())
|
||||||
|
initialize()
|
||||||
|
failIfError()
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val disconnect by metaAction({
|
||||||
|
info = "Disconnect the program from the device if it is connected"
|
||||||
|
}) {
|
||||||
|
if (port != null) {
|
||||||
|
stop()
|
||||||
|
port?.close()
|
||||||
|
}
|
||||||
|
port = null
|
||||||
|
updateLogical(connected, false)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val timeout by property(MetaConverter.duration, PiMotionMasterDevice::timeoutValue) {
|
||||||
|
info = "Timeout"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Axis(
|
||||||
|
val mm: PiMotionMasterDevice,
|
||||||
|
val axisId: String
|
||||||
|
) : DeviceBySpec<Axis>(Axis, mm.context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Move to head device and abstract
|
||||||
|
*/
|
||||||
private suspend fun readAxisBoolean(command: String): Boolean =
|
private suspend fun readAxisBoolean(command: String): Boolean =
|
||||||
requestAndParse(command, axisId)[axisId]?.toIntOrNull()
|
(mm.requestAndParse(command, axisId)[axisId]?.toIntOrNull()
|
||||||
?: error("Malformed $command response. Should include integer value for $axisId") != 0
|
?: error("Malformed $command response. Should include integer value for $axisId")) != 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Move to head device and abstract
|
||||||
|
*/
|
||||||
private suspend fun writeAxisBoolean(command: String, value: Boolean): Boolean {
|
private suspend fun writeAxisBoolean(command: String, value: Boolean): Boolean {
|
||||||
val boolean = if (value) {
|
val boolean = if (value) {
|
||||||
"1"
|
"1"
|
||||||
} else {
|
} else {
|
||||||
"0"
|
"0"
|
||||||
}
|
}
|
||||||
send(command, axisId, boolean)
|
mm.send(command, axisId, boolean)
|
||||||
failIfError()
|
mm.failIfError()
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun axisBooleanProperty(command: String, descriptorBuilder: PropertyDescriptor.() -> Unit = {}) =
|
|
||||||
writingBoolean(
|
|
||||||
getter = { readAxisBoolean("$command?") },
|
|
||||||
setter = { _, newValue ->
|
|
||||||
writeAxisBoolean(command, newValue)
|
|
||||||
},
|
|
||||||
descriptorBuilder = descriptorBuilder
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun axisNumberProperty(command: String, descriptorBuilder: PropertyDescriptor.() -> Unit = {}) =
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
val enabled by axisBooleanProperty("EAX") {
|
|
||||||
info = "Motor enable state."
|
|
||||||
}
|
|
||||||
|
|
||||||
val halt: DeviceAction by acting {
|
|
||||||
send("HLT", axisId)
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
val onTarget: TypedReadOnlyDeviceProperty<Boolean> by readingBoolean(
|
|
||||||
descriptorBuilder = {
|
|
||||||
info = "Queries the on-target state of the specified axis."
|
|
||||||
},
|
|
||||||
getter = {
|
|
||||||
readAxisBoolean("ONT?")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val reference: ReadOnlyDeviceProperty by readingBoolean(
|
|
||||||
descriptorBuilder = {
|
|
||||||
info = "Get Referencing Result"
|
|
||||||
},
|
|
||||||
getter = {
|
|
||||||
readAxisBoolean("FRF?")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val moveToReference by acting {
|
|
||||||
send("FRF", axisId)
|
|
||||||
}
|
|
||||||
|
|
||||||
val minPosition by readingDouble(
|
|
||||||
descriptorBuilder = {
|
|
||||||
info = "Minimal position value for the axis"
|
|
||||||
},
|
|
||||||
getter = {
|
|
||||||
requestAndParse("TMN?", axisId)[axisId]?.toDoubleOrNull()
|
|
||||||
?: error("Malformed `TMN?` response. Should include float value for $axisId")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val maxPosition by readingDouble(
|
|
||||||
descriptorBuilder = {
|
|
||||||
info = "Maximal position value for the axis"
|
|
||||||
},
|
|
||||||
getter = {
|
|
||||||
requestAndParse("TMX?", axisId)[axisId]?.toDoubleOrNull()
|
|
||||||
?: error("Malformed `TMX?` response. Should include float value for $axisId")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val position by readingDouble(
|
|
||||||
descriptorBuilder = {
|
|
||||||
info = "The current axis position."
|
|
||||||
},
|
|
||||||
getter = {
|
|
||||||
requestAndParse("POS?", axisId)[axisId]?.toDoubleOrNull()
|
|
||||||
?: error("Malformed `POS?` response. Should include float value for $axisId")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val openLoopTarget: DeviceProperty by axisNumberProperty("OMA") {
|
|
||||||
info = "Position for open-loop operation."
|
|
||||||
}
|
|
||||||
|
|
||||||
val closedLoop: TypedDeviceProperty<Boolean> by axisBooleanProperty("SVO") {
|
|
||||||
info = "Servo closed loop mode"
|
|
||||||
}
|
|
||||||
|
|
||||||
val velocity: TypedDeviceProperty<Double> by axisNumberProperty("VEL") {
|
|
||||||
info = "Velocity value for closed-loop operation"
|
|
||||||
}
|
|
||||||
|
|
||||||
val move by acting {
|
|
||||||
val target = it.double ?: it?.get("target").double ?: error("Unacceptable target value $it")
|
|
||||||
closedLoop.write(true)
|
|
||||||
//optionally set velocity
|
|
||||||
it?.get("velocity").double?.let { v ->
|
|
||||||
velocity.write(v)
|
|
||||||
}
|
|
||||||
targetPosition.write(target)
|
|
||||||
//read `onTarget` and `position` properties in a cycle until movement is complete
|
|
||||||
while (!onTarget.readTyped(true)) {
|
|
||||||
position.read(true)
|
|
||||||
delay(200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun move(target: Double) {
|
suspend fun move(target: Double) {
|
||||||
move(target.asMeta())
|
move(target.asMeta())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
companion object : Factory<PiMotionMasterDevice> {
|
companion object : DeviceSpec<Axis>() {
|
||||||
override fun invoke(meta: Meta, context: Context): PiMotionMasterDevice = PiMotionMasterDevice(context)
|
|
||||||
|
private fun axisBooleanProperty(
|
||||||
|
command: String,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
|
) = booleanProperty(
|
||||||
|
read = {
|
||||||
|
readAxisBoolean("$command?")
|
||||||
|
},
|
||||||
|
write = {
|
||||||
|
writeAxisBoolean(command, it)
|
||||||
|
},
|
||||||
|
descriptorBuilder = descriptorBuilder
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun axisNumberProperty(
|
||||||
|
command: String,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
|
) = doubleProperty(
|
||||||
|
read = {
|
||||||
|
mm.requestAndParse("$command?", axisId)[axisId]?.toDoubleOrNull()
|
||||||
|
?: error("Malformed $command response. Should include float value for $axisId")
|
||||||
|
},
|
||||||
|
write = { newValue ->
|
||||||
|
mm.send(command, axisId, newValue.toString())
|
||||||
|
mm.failIfError()
|
||||||
|
},
|
||||||
|
descriptorBuilder = descriptorBuilder
|
||||||
|
)
|
||||||
|
|
||||||
|
val enabled by axisBooleanProperty("EAX") {
|
||||||
|
info = "Motor enable state."
|
||||||
|
}
|
||||||
|
|
||||||
|
val halt by unitAction {
|
||||||
|
mm.send("HLT", axisId)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
val onTarget by booleanProperty({
|
||||||
|
info = "Queries the on-target state of the specified axis."
|
||||||
|
}) {
|
||||||
|
readAxisBoolean("ONT?")
|
||||||
|
}
|
||||||
|
|
||||||
|
val reference by booleanProperty({
|
||||||
|
info = "Get Referencing Result"
|
||||||
|
}) {
|
||||||
|
readAxisBoolean("FRF?")
|
||||||
|
}
|
||||||
|
|
||||||
|
val moveToReference by unitAction {
|
||||||
|
mm.send("FRF", axisId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val minPosition by doubleProperty({
|
||||||
|
info = "Minimal position value for the axis"
|
||||||
|
}) {
|
||||||
|
mm.requestAndParse("TMN?", axisId)[axisId]?.toDoubleOrNull()
|
||||||
|
?: error("Malformed `TMN?` response. Should include float value for $axisId")
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxPosition by doubleProperty({
|
||||||
|
info = "Maximal position value for the axis"
|
||||||
|
}) {
|
||||||
|
mm.requestAndParse("TMX?", axisId)[axisId]?.toDoubleOrNull()
|
||||||
|
?: error("Malformed `TMX?` response. Should include float value for $axisId")
|
||||||
|
}
|
||||||
|
|
||||||
|
val position by doubleProperty({
|
||||||
|
info = "The current axis position."
|
||||||
|
}) {
|
||||||
|
mm.requestAndParse("POS?", axisId)[axisId]?.toDoubleOrNull()
|
||||||
|
?: error("Malformed `POS?` response. Should include float value for $axisId")
|
||||||
|
}
|
||||||
|
|
||||||
|
val openLoopTarget by axisNumberProperty("OMA") {
|
||||||
|
info = "Position for open-loop operation."
|
||||||
|
}
|
||||||
|
|
||||||
|
val closedLoop by axisBooleanProperty("SVO") {
|
||||||
|
info = "Servo closed loop mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
val velocity by axisNumberProperty("VEL") {
|
||||||
|
info = "Velocity value for closed-loop operation"
|
||||||
|
}
|
||||||
|
|
||||||
|
val move by metaAction {
|
||||||
|
val target = it.double ?: it?.get("target").double ?: error("Unacceptable target value $it")
|
||||||
|
closedLoop.write(true)
|
||||||
|
//optionally set velocity
|
||||||
|
it?.get("velocity").double?.let { v ->
|
||||||
|
velocity.write(v)
|
||||||
|
}
|
||||||
|
targetPosition.write(target)
|
||||||
|
//read `onTarget` and `position` properties in a cycle until movement is complete
|
||||||
|
while (!onTarget.read()) {
|
||||||
|
position.read()
|
||||||
|
delay(200)
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -3,58 +3,67 @@ package ru.mipt.npm.devices.pimotionmaster
|
|||||||
import javafx.beans.property.ObjectPropertyBase
|
import javafx.beans.property.ObjectPropertyBase
|
||||||
import javafx.beans.property.Property
|
import javafx.beans.property.Property
|
||||||
import javafx.beans.property.ReadOnlyProperty
|
import javafx.beans.property.ReadOnlyProperty
|
||||||
import kotlinx.coroutines.flow.catch
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import ru.mipt.npm.controls.api.Device
|
import ru.mipt.npm.controls.api.Device
|
||||||
import ru.mipt.npm.controls.base.TypedDeviceProperty
|
import ru.mipt.npm.controls.spec.DevicePropertySpec
|
||||||
import ru.mipt.npm.controls.base.TypedReadOnlyDeviceProperty
|
import ru.mipt.npm.controls.spec.WritableDevicePropertySpec
|
||||||
|
import ru.mipt.npm.controls.spec.onPropertyChange
|
||||||
|
import ru.mipt.npm.controls.spec.write
|
||||||
import space.kscience.dataforge.context.info
|
import space.kscience.dataforge.context.info
|
||||||
import space.kscience.dataforge.context.logger
|
import space.kscience.dataforge.context.logger
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
fun <T : Any> TypedReadOnlyDeviceProperty<T>.fxProperty(ownerDevice: Device?): ReadOnlyProperty<T> =
|
/**
|
||||||
object : ObjectPropertyBase<T>() {
|
* Bind a FX property to a device property with a given [spec]
|
||||||
override fun getBean(): Any? = ownerDevice
|
*/
|
||||||
override fun getName(): String = this@fxProperty.name
|
fun <D : Device, T : Any> Device.fxProperty(
|
||||||
|
spec: DevicePropertySpec<D, T>
|
||||||
|
): ReadOnlyProperty<T> = object : ObjectPropertyBase<T>() {
|
||||||
|
override fun getBean(): Any = this
|
||||||
|
override fun getName(): String = spec.name
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//Read incoming changes
|
//Read incoming changes
|
||||||
flowTyped().onEach {
|
onPropertyChange(spec) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
runLater {
|
runLater {
|
||||||
|
try {
|
||||||
set(it)
|
set(it)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
logger.info { "Failed to set property $name to $it" }
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
invalidated()
|
|
||||||
}
|
}
|
||||||
}.catch {
|
} else {
|
||||||
ownerDevice?.logger?.info { "Failed to set property $name to $it" }
|
invalidated()
|
||||||
}.launchIn(scope)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fun <T : Any> TypedDeviceProperty<T>.fxProperty(ownerDevice: Device?): Property<T> =
|
|
||||||
object : ObjectPropertyBase<T>() {
|
fun <D : Device, T : Any> D.fxProperty(spec: WritableDevicePropertySpec<D, T>): Property<T> =
|
||||||
override fun getBean(): Any? = ownerDevice
|
object : ObjectPropertyBase<T>() {
|
||||||
override fun getName(): String = this@fxProperty.name
|
override fun getBean(): Any = this
|
||||||
|
override fun getName(): String = spec.name
|
||||||
init {
|
|
||||||
//Read incoming changes
|
init {
|
||||||
flowTyped().onEach {
|
//Read incoming changes
|
||||||
if (it != null) {
|
onPropertyChange(spec) {
|
||||||
runLater {
|
if (it != null) {
|
||||||
set(it)
|
runLater {
|
||||||
}
|
try {
|
||||||
} else {
|
set(it)
|
||||||
invalidated()
|
} catch (ex: Throwable) {
|
||||||
}
|
logger.info { "Failed to set property $name to $it" }
|
||||||
}.catch {
|
}
|
||||||
ownerDevice?.logger?.info { "Failed to set property $name to $it" }
|
}
|
||||||
}.launchIn(scope)
|
} else {
|
||||||
|
invalidated()
|
||||||
onChange {
|
}
|
||||||
typedValue = it
|
}
|
||||||
|
|
||||||
|
onChange { newValue ->
|
||||||
|
if (newValue != null) {
|
||||||
|
write(spec, newValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
|||||||
enableFeaturePreview("VERSION_CATALOGS")
|
enableFeaturePreview("VERSION_CATALOGS")
|
||||||
|
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
val toolsVersion = "0.10.4"
|
val toolsVersion = "0.10.5"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven("https://repo.kotlin.link")
|
maven("https://repo.kotlin.link")
|
||||||
@ -28,7 +28,7 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
versionCatalogs {
|
versionCatalogs {
|
||||||
create("npm") {
|
create("npm") {
|
||||||
from("ru.mipt.npm:version-catalog:0.10.4")
|
from("ru.mipt.npm:version-catalog:0.10.5")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user