Add DeviceConstructor
This commit is contained in:
parent
0443fdc3c0
commit
825f1a4d04
@ -0,0 +1,126 @@
|
|||||||
|
package space.kscience.controls.constructor
|
||||||
|
|
||||||
|
import space.kscience.controls.api.Device
|
||||||
|
import space.kscience.controls.api.PropertyDescriptor
|
||||||
|
import space.kscience.controls.manager.DeviceManager
|
||||||
|
import space.kscience.dataforge.context.Factory
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.asName
|
||||||
|
import kotlin.properties.PropertyDelegateProvider
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base for strongly typed device constructor blocks. Has additional delegates for type-safe devices
|
||||||
|
*/
|
||||||
|
public abstract class DeviceConstructor(
|
||||||
|
deviceManager: DeviceManager,
|
||||||
|
meta: Meta,
|
||||||
|
) : DeviceGroup(deviceManager, meta) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a device, provided by a given [factory] and
|
||||||
|
*/
|
||||||
|
public fun <D : Device> device(
|
||||||
|
factory: Factory<D>,
|
||||||
|
meta: Meta? = null,
|
||||||
|
nameOverride: Name? = null,
|
||||||
|
metaLocation: Name? = null,
|
||||||
|
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, D>> =
|
||||||
|
PropertyDelegateProvider { _: DeviceConstructor, property: KProperty<*> ->
|
||||||
|
val name = nameOverride ?: property.name.asName()
|
||||||
|
val device = registerDevice(name, factory, meta, metaLocation ?: name)
|
||||||
|
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
||||||
|
device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <D : Device> device(
|
||||||
|
device: D,
|
||||||
|
nameOverride: Name? = null,
|
||||||
|
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, D>> =
|
||||||
|
PropertyDelegateProvider { _: DeviceConstructor, property: KProperty<*> ->
|
||||||
|
val name = nameOverride ?: property.name.asName()
|
||||||
|
registerDevice(name, device)
|
||||||
|
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
||||||
|
device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a property and provide a direct reader for it
|
||||||
|
*/
|
||||||
|
public fun <T : Any> property(
|
||||||
|
state: DeviceState<T>,
|
||||||
|
nameOverride: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit,
|
||||||
|
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> =
|
||||||
|
PropertyDelegateProvider { _: DeviceConstructor, property ->
|
||||||
|
val name = nameOverride ?: property.name
|
||||||
|
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
|
||||||
|
registerProperty(descriptor, state)
|
||||||
|
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
||||||
|
state.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register external state as a property
|
||||||
|
*/
|
||||||
|
public fun <T : Any> property(
|
||||||
|
metaConverter: MetaConverter<T>,
|
||||||
|
reader: suspend () -> T,
|
||||||
|
readInterval: Duration,
|
||||||
|
initialState: T,
|
||||||
|
nameOverride: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit,
|
||||||
|
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> = property(
|
||||||
|
DeviceState.external(this, metaConverter, readInterval, initialState, reader),
|
||||||
|
nameOverride, descriptorBuilder
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a mutable property and provide a direct reader for it
|
||||||
|
*/
|
||||||
|
public fun <T : Any> mutableProperty(
|
||||||
|
state: MutableDeviceState<T>,
|
||||||
|
nameOverride: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit,
|
||||||
|
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> =
|
||||||
|
PropertyDelegateProvider { _: DeviceConstructor, property ->
|
||||||
|
val name = nameOverride ?: property.name
|
||||||
|
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
|
||||||
|
registerProperty(descriptor, state)
|
||||||
|
object : ReadWriteProperty<DeviceConstructor, T> {
|
||||||
|
override fun getValue(thisRef: DeviceConstructor, property: KProperty<*>): T = state.value
|
||||||
|
|
||||||
|
override fun setValue(thisRef: DeviceConstructor, property: KProperty<*>, value: T) {
|
||||||
|
state.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register external state as a property
|
||||||
|
*/
|
||||||
|
public fun <T : Any> mutableProperty(
|
||||||
|
metaConverter: MetaConverter<T>,
|
||||||
|
reader: suspend () -> T,
|
||||||
|
writer: suspend (T) -> Unit,
|
||||||
|
readInterval: Duration,
|
||||||
|
initialState: T,
|
||||||
|
nameOverride: String? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit,
|
||||||
|
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> = mutableProperty(
|
||||||
|
DeviceState.external(this, metaConverter, readInterval, initialState, reader, writer),
|
||||||
|
nameOverride,
|
||||||
|
descriptorBuilder
|
||||||
|
)
|
||||||
|
}
|
@ -4,6 +4,7 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import space.kscience.controls.api.*
|
import space.kscience.controls.api.*
|
||||||
|
import space.kscience.controls.api.DeviceLifecycleState.*
|
||||||
import space.kscience.controls.manager.DeviceManager
|
import space.kscience.controls.manager.DeviceManager
|
||||||
import space.kscience.controls.manager.install
|
import space.kscience.controls.manager.install
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
@ -23,7 +24,7 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
/**
|
/**
|
||||||
* A mutable group of devices and properties to be used for lightweight design and simulations.
|
* A mutable group of devices and properties to be used for lightweight design and simulations.
|
||||||
*/
|
*/
|
||||||
public class DeviceGroup(
|
public open class DeviceGroup(
|
||||||
public val deviceManager: DeviceManager,
|
public val deviceManager: DeviceManager,
|
||||||
override val meta: Meta,
|
override val meta: Meta,
|
||||||
) : DeviceHub, CachingDevice {
|
) : DeviceHub, CachingDevice {
|
||||||
@ -63,15 +64,28 @@ public class DeviceGroup(
|
|||||||
|
|
||||||
override val devices: Map<NameToken, Device> = _devices
|
override val devices: Map<NameToken, Device> = _devices
|
||||||
|
|
||||||
public fun <D : Device> device(token: NameToken, device: D): D {
|
/**
|
||||||
check(_devices[token] == null) { "A child device with name $token already exists" }
|
* Register and initialize (synchronize child's lifecycle state with group state) a new device in this group
|
||||||
|
*/
|
||||||
|
@OptIn(DFExperimental::class)
|
||||||
|
public fun <D : Device> registerDevice(token: NameToken, device: D): D {
|
||||||
|
require(_devices[token] == null) { "A child device with name $token already exists" }
|
||||||
|
//start or stop the child if needed
|
||||||
|
when (lifecycleState) {
|
||||||
|
STARTING, STARTED -> launch { device.start() }
|
||||||
|
STOPPED -> device.stop()
|
||||||
|
ERROR -> {}
|
||||||
|
}
|
||||||
_devices[token] = device
|
_devices[token] = device
|
||||||
return device
|
return device
|
||||||
}
|
}
|
||||||
|
|
||||||
private val properties: MutableMap<Name, Property> = hashMapOf()
|
private val properties: MutableMap<Name, Property> = hashMapOf()
|
||||||
|
|
||||||
public fun property(descriptor: PropertyDescriptor, state: DeviceState<out Any>) {
|
/**
|
||||||
|
* Register a new property based on [DeviceState]. Properties could be modified dynamically
|
||||||
|
*/
|
||||||
|
public fun registerProperty(descriptor: PropertyDescriptor, state: DeviceState<out Any>) {
|
||||||
val name = descriptor.name.parseAsName()
|
val name = descriptor.name.parseAsName()
|
||||||
require(properties[name] == null) { "Can't add property with name $name. It already exists." }
|
require(properties[name] == null) { "Can't add property with name $name. It already exists." }
|
||||||
properties[name] = Property(state, descriptor)
|
properties[name] = Property(state, descriptor)
|
||||||
@ -112,8 +126,8 @@ public class DeviceGroup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.STOPPED
|
override var lifecycleState: DeviceLifecycleState = STOPPED
|
||||||
private set(value) {
|
protected set(value) {
|
||||||
if (field != value) {
|
if (field != value) {
|
||||||
launch {
|
launch {
|
||||||
sharedMessageFlow.emit(
|
sharedMessageFlow.emit(
|
||||||
@ -127,12 +141,12 @@ public class DeviceGroup(
|
|||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
override suspend fun start() {
|
override suspend fun start() {
|
||||||
lifecycleState = DeviceLifecycleState.STARTING
|
lifecycleState = STARTING
|
||||||
super.start()
|
super.start()
|
||||||
devices.values.forEach {
|
devices.values.forEach {
|
||||||
it.start()
|
it.start()
|
||||||
}
|
}
|
||||||
lifecycleState = DeviceLifecycleState.STARTED
|
lifecycleState = STARTED
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
@ -141,7 +155,7 @@ public class DeviceGroup(
|
|||||||
it.stop()
|
it.stop()
|
||||||
}
|
}
|
||||||
super.stop()
|
super.stop()
|
||||||
lifecycleState = DeviceLifecycleState.STOPPED
|
lifecycleState = STOPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
@ -149,7 +163,7 @@ public class DeviceGroup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun DeviceManager.deviceGroup(
|
public fun DeviceManager.registerDeviceGroup(
|
||||||
name: String = "@group",
|
name: String = "@group",
|
||||||
meta: Meta = Meta.EMPTY,
|
meta: Meta = Meta.EMPTY,
|
||||||
block: DeviceGroup.() -> Unit,
|
block: DeviceGroup.() -> Unit,
|
||||||
@ -159,11 +173,11 @@ public fun DeviceManager.deviceGroup(
|
|||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Context.deviceGroup(
|
public fun Context.registerDeviceGroup(
|
||||||
name: String = "@group",
|
name: String = "@group",
|
||||||
meta: Meta = Meta.EMPTY,
|
meta: Meta = Meta.EMPTY,
|
||||||
block: DeviceGroup.() -> Unit,
|
block: DeviceGroup.() -> Unit,
|
||||||
): DeviceGroup = request(DeviceManager).deviceGroup(name, meta, block)
|
): DeviceGroup = request(DeviceManager).registerDeviceGroup(name, meta, block)
|
||||||
|
|
||||||
private fun DeviceGroup.getOrCreateGroup(name: Name): DeviceGroup {
|
private fun DeviceGroup.getOrCreateGroup(name: Name): DeviceGroup {
|
||||||
return when (name.length) {
|
return when (name.length) {
|
||||||
@ -171,7 +185,7 @@ private fun DeviceGroup.getOrCreateGroup(name: Name): DeviceGroup {
|
|||||||
1 -> {
|
1 -> {
|
||||||
val token = name.first()
|
val token = name.first()
|
||||||
when (val d = devices[token]) {
|
when (val d = devices[token]) {
|
||||||
null -> device(
|
null -> registerDevice(
|
||||||
token,
|
token,
|
||||||
DeviceGroup(deviceManager, meta[token] ?: Meta.EMPTY)
|
DeviceGroup(deviceManager, meta[token] ?: Meta.EMPTY)
|
||||||
)
|
)
|
||||||
@ -187,79 +201,99 @@ private fun DeviceGroup.getOrCreateGroup(name: Name): DeviceGroup {
|
|||||||
/**
|
/**
|
||||||
* Register a device at given [name] path
|
* Register a device at given [name] path
|
||||||
*/
|
*/
|
||||||
public fun <D : Device> DeviceGroup.device(name: Name, device: D): D {
|
public fun <D : Device> DeviceGroup.registerDevice(name: Name, device: D): D {
|
||||||
return when (name.length) {
|
return when (name.length) {
|
||||||
0 -> error("Can't use empty name for a child device")
|
0 -> error("Can't use empty name for a child device")
|
||||||
1 -> device(name.first(), device)
|
1 -> registerDevice(name.first(), device)
|
||||||
else -> getOrCreateGroup(name.cutLast()).device(name.tokens.last(), device)
|
else -> getOrCreateGroup(name.cutLast()).registerDevice(name.tokens.last(), device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <D: Device> DeviceGroup.device(name: String, device: D): D = device(name.parseAsName(), device)
|
public fun <D : Device> DeviceGroup.registerDevice(name: String, device: D): D = registerDevice(name.parseAsName(), device)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a device creating intermediate groups if necessary. If device with given [name] already exists, throws an error.
|
* Add a device creating intermediate groups if necessary. If device with given [name] already exists, throws an error.
|
||||||
|
* @param name the name of the device in the group
|
||||||
|
* @param factory a factory used to create a device
|
||||||
|
* @param deviceMeta meta override for this specific device
|
||||||
|
* @param metaLocation location of the template meta in parent group meta
|
||||||
*/
|
*/
|
||||||
public fun DeviceGroup.device(name: Name, factory: Factory<Device>, deviceMeta: Meta? = null): Device {
|
public fun <D : Device> DeviceGroup.registerDevice(
|
||||||
val newDevice = factory.build(deviceManager.context, Laminate(deviceMeta, meta[name]))
|
name: Name,
|
||||||
device(name, newDevice)
|
factory: Factory<D>,
|
||||||
|
deviceMeta: Meta? = null,
|
||||||
|
metaLocation: Name = name,
|
||||||
|
): D {
|
||||||
|
val newDevice = factory.build(deviceManager.context, Laminate(deviceMeta, meta[metaLocation]))
|
||||||
|
registerDevice(name, newDevice)
|
||||||
return newDevice
|
return newDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun DeviceGroup.device(
|
public fun <D : Device> DeviceGroup.registerDevice(
|
||||||
name: String,
|
name: String,
|
||||||
factory: Factory<Device>,
|
factory: Factory<D>,
|
||||||
|
metaLocation: Name = name.parseAsName(),
|
||||||
metaBuilder: (MutableMeta.() -> Unit)? = null,
|
metaBuilder: (MutableMeta.() -> Unit)? = null,
|
||||||
): Device = device(name.parseAsName(), factory, metaBuilder?.let { Meta(it) })
|
): D = registerDevice(name.parseAsName(), factory, metaBuilder?.let { Meta(it) }, metaLocation)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or edit a group with a given [name].
|
* Create or edit a group with a given [name].
|
||||||
*/
|
*/
|
||||||
public fun DeviceGroup.deviceGroup(name: Name, block: DeviceGroup.() -> Unit): DeviceGroup =
|
public fun DeviceGroup.registerDeviceGroup(name: Name, block: DeviceGroup.() -> Unit): DeviceGroup =
|
||||||
getOrCreateGroup(name).apply(block)
|
getOrCreateGroup(name).apply(block)
|
||||||
|
|
||||||
public fun DeviceGroup.deviceGroup(name: String, block: DeviceGroup.() -> Unit): DeviceGroup =
|
public fun DeviceGroup.registerDeviceGroup(name: String, block: DeviceGroup.() -> Unit): DeviceGroup =
|
||||||
deviceGroup(name.parseAsName(), block)
|
registerDeviceGroup(name.parseAsName(), block)
|
||||||
|
|
||||||
public fun <T : Any> DeviceGroup.property(
|
/**
|
||||||
|
* Register read-only property based on [state]
|
||||||
|
*/
|
||||||
|
public fun <T : Any> DeviceGroup.registerProperty(
|
||||||
name: String,
|
name: String,
|
||||||
state: DeviceState<T>,
|
state: DeviceState<T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
): DeviceState<T> {
|
) {
|
||||||
property(
|
registerProperty(
|
||||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||||
state
|
state
|
||||||
)
|
)
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> DeviceGroup.mutableProperty(
|
/**
|
||||||
|
* Register a mutable property based on mutable [state]
|
||||||
|
*/
|
||||||
|
public fun <T : Any> DeviceGroup.registerMutableProperty(
|
||||||
name: String,
|
name: String,
|
||||||
state: MutableDeviceState<T>,
|
state: MutableDeviceState<T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
): MutableDeviceState<T> {
|
) {
|
||||||
property(
|
registerProperty(
|
||||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||||
state
|
state
|
||||||
)
|
)
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> DeviceGroup.virtualProperty(
|
|
||||||
name: String,
|
|
||||||
initialValue: T,
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
): MutableDeviceState<T> {
|
|
||||||
val state = VirtualDeviceState<T>(converter, initialValue)
|
|
||||||
return mutableProperty(name, state, descriptorBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a virtual [MutableDeviceState], but do not register it to a device
|
* Create a virtual [MutableDeviceState], but do not register it to a device
|
||||||
*/
|
*/
|
||||||
@Suppress("UnusedReceiverParameter")
|
@Suppress("UnusedReceiverParameter")
|
||||||
public fun <T : Any> DeviceGroup.standAloneProperty(
|
public fun <T : Any> DeviceGroup.state(
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
initialValue: T,
|
||||||
|
): MutableDeviceState<T> = VirtualDeviceState<T>(converter, initialValue)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new virtual mutable state and a property based on it.
|
||||||
|
* @return the mutable state used in property
|
||||||
|
*/
|
||||||
|
public fun <T : Any> DeviceGroup.registerVirtualProperty(
|
||||||
|
name: String,
|
||||||
initialValue: T,
|
initialValue: T,
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
): MutableDeviceState<T> = VirtualDeviceState<T>(converter, initialValue)
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
): MutableDeviceState<T> {
|
||||||
|
val state = state(converter, initialValue)
|
||||||
|
registerMutableProperty(name, state, descriptorBuilder)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package space.kscience.controls.constructor
|
package space.kscience.controls.constructor
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import space.kscience.controls.api.Device
|
import space.kscience.controls.api.Device
|
||||||
@ -9,6 +11,7 @@ import space.kscience.controls.spec.MutableDevicePropertySpec
|
|||||||
import space.kscience.controls.spec.name
|
import space.kscience.controls.spec.name
|
||||||
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.time.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An observable state of a device
|
* An observable state of a device
|
||||||
@ -18,6 +21,8 @@ public interface DeviceState<T> {
|
|||||||
public val value: T
|
public val value: T
|
||||||
|
|
||||||
public val valueFlow: Flow<T>
|
public val valueFlow: Flow<T>
|
||||||
|
|
||||||
|
public companion object
|
||||||
}
|
}
|
||||||
|
|
||||||
public val <T> DeviceState<T>.metaFlow: Flow<Meta> get() = valueFlow.map(converter::objectToMeta)
|
public val <T> DeviceState<T>.metaFlow: Flow<Meta> get() = valueFlow.map(converter::objectToMeta)
|
||||||
@ -55,7 +60,7 @@ private open class BoundDeviceState<T>(
|
|||||||
override val converter: MetaConverter<T>,
|
override val converter: MetaConverter<T>,
|
||||||
val device: Device,
|
val device: Device,
|
||||||
val propertyName: String,
|
val propertyName: String,
|
||||||
private val initialValue: T,
|
initialValue: T,
|
||||||
) : DeviceState<T> {
|
) : DeviceState<T> {
|
||||||
|
|
||||||
override val valueFlow: StateFlow<T> = device.messageFlow.filterIsInstance<PropertyChangedMessage>().filter {
|
override val valueFlow: StateFlow<T> = device.messageFlow.filterIsInstance<PropertyChangedMessage>().filter {
|
||||||
@ -121,3 +126,58 @@ public suspend fun <D : Device, T> D.bindMutableStateToProperty(
|
|||||||
): MutableDeviceState<T> = bindMutableStateToProperty(propertySpec.name, propertySpec.converter)
|
): MutableDeviceState<T> = bindMutableStateToProperty(propertySpec.name, propertySpec.converter)
|
||||||
|
|
||||||
|
|
||||||
|
private open class ExternalState<T>(
|
||||||
|
val scope: CoroutineScope,
|
||||||
|
override val converter: MetaConverter<T>,
|
||||||
|
val readInterval: Duration,
|
||||||
|
initialValue: T,
|
||||||
|
val reader: suspend () -> T,
|
||||||
|
) : DeviceState<T> {
|
||||||
|
|
||||||
|
protected val flow: StateFlow<T> = flow {
|
||||||
|
while (true) {
|
||||||
|
delay(readInterval)
|
||||||
|
emit(reader())
|
||||||
|
}
|
||||||
|
}.stateIn(scope, SharingStarted.Eagerly, initialValue)
|
||||||
|
|
||||||
|
override val value: T get() = flow.value
|
||||||
|
override val valueFlow: Flow<T> get() = flow
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [DeviceState] which is constructed by periodically reading external value
|
||||||
|
*/
|
||||||
|
public fun <T> DeviceState.Companion.external(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
readInterval: Duration,
|
||||||
|
initialValue: T,
|
||||||
|
reader: suspend () -> T,
|
||||||
|
): DeviceState<T> = ExternalState(scope, converter, readInterval, initialValue, reader)
|
||||||
|
|
||||||
|
private class MutableExternalState<T>(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
readInterval: Duration,
|
||||||
|
initialValue: T,
|
||||||
|
reader: suspend () -> T,
|
||||||
|
val writer: suspend (T) -> Unit,
|
||||||
|
) : ExternalState<T>(scope, converter, readInterval, initialValue, reader), MutableDeviceState<T> {
|
||||||
|
override var value: T
|
||||||
|
get() = super.value
|
||||||
|
set(value) {
|
||||||
|
scope.launch {
|
||||||
|
writer(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T> DeviceState.Companion.external(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
readInterval: Duration,
|
||||||
|
initialValue: T,
|
||||||
|
reader: suspend () -> T,
|
||||||
|
writer: suspend (T) -> Unit,
|
||||||
|
): MutableDeviceState<T> = MutableExternalState(scope, converter, readInterval, initialValue, reader, writer)
|
@ -8,6 +8,7 @@ import space.kscience.controls.api.Device
|
|||||||
import space.kscience.controls.manager.clock
|
import space.kscience.controls.manager.clock
|
||||||
import space.kscience.controls.spec.*
|
import space.kscience.controls.spec.*
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
|
import space.kscience.dataforge.context.Factory
|
||||||
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.meta.transformations.MetaConverter
|
||||||
@ -84,12 +85,15 @@ public class VirtualDrive(
|
|||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
updateJob?.cancel()
|
updateJob?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public fun factory(
|
||||||
|
mass: Double,
|
||||||
|
positionState: MutableDeviceState<Double>,
|
||||||
|
): Factory<Drive> = Factory { context, _ ->
|
||||||
|
VirtualDrive(context, mass, positionState)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public suspend fun Drive.stateOfForce(): MutableDeviceState<Double> = bindMutableStateToProperty(Drive.force)
|
public suspend fun Drive.stateOfForce(): MutableDeviceState<Double> = bindMutableStateToProperty(Drive.force)
|
||||||
|
|
||||||
public fun DeviceGroup.virtualDrive(
|
|
||||||
name: String,
|
|
||||||
mass: Double,
|
|
||||||
positionState: MutableDeviceState<Double>,
|
|
||||||
): VirtualDrive = device(name, VirtualDrive(context, mass, positionState))
|
|
@ -8,7 +8,7 @@ import space.kscience.controls.spec.DevicePropertySpec
|
|||||||
import space.kscience.controls.spec.DeviceSpec
|
import space.kscience.controls.spec.DeviceSpec
|
||||||
import space.kscience.controls.spec.booleanProperty
|
import space.kscience.controls.spec.booleanProperty
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.names.parseAsName
|
import space.kscience.dataforge.context.Factory
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,6 +20,9 @@ public interface LimitSwitch : Device {
|
|||||||
|
|
||||||
public companion object : DeviceSpec<LimitSwitch>() {
|
public companion object : DeviceSpec<LimitSwitch>() {
|
||||||
public val locked: DevicePropertySpec<LimitSwitch, Boolean> by booleanProperty { locked }
|
public val locked: DevicePropertySpec<LimitSwitch, Boolean> by booleanProperty { locked }
|
||||||
|
public fun factory(lockedState: DeviceState<Boolean>): Factory<LimitSwitch> = Factory { context, _ ->
|
||||||
|
VirtualLimitSwitch(context, lockedState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +42,3 @@ public class VirtualLimitSwitch(
|
|||||||
|
|
||||||
override val locked: Boolean get() = lockedState.value
|
override val locked: Boolean get() = lockedState.value
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun DeviceGroup.virtualLimitSwitch(name: String, lockedState: DeviceState<Boolean>): VirtualLimitSwitch =
|
|
||||||
device(name.parseAsName(), VirtualLimitSwitch(context, lockedState))
|
|
@ -79,4 +79,4 @@ public fun DeviceGroup.pid(
|
|||||||
name: String,
|
name: String,
|
||||||
drive: Drive,
|
drive: Drive,
|
||||||
pidParameters: PidParameters,
|
pidParameters: PidParameters,
|
||||||
): PidRegulator = device(name, PidRegulator(drive, pidParameters))
|
): PidRegulator = registerDevice(name, PidRegulator(drive, pidParameters))
|
@ -39,3 +39,9 @@ public class DoubleRangeState(
|
|||||||
*/
|
*/
|
||||||
public val atEndState: DeviceState<Boolean> = map(MetaConverter.boolean) { it >= range.endInclusive }
|
public val atEndState: DeviceState<Boolean> = map(MetaConverter.boolean) { it >= range.endInclusive }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UnusedReceiverParameter")
|
||||||
|
public fun DeviceGroup.rangeState(
|
||||||
|
initialValue: Double,
|
||||||
|
range: ClosedFloatingPointRange<Double>,
|
||||||
|
): DoubleRangeState = DoubleRangeState(initialValue, range)
|
@ -147,7 +147,7 @@ internal class MetaStructureCodec(
|
|||||||
"Float" -> member.value?.numberOrNull?.toFloat()
|
"Float" -> member.value?.numberOrNull?.toFloat()
|
||||||
"Double" -> member.value?.numberOrNull?.toDouble()
|
"Double" -> member.value?.numberOrNull?.toDouble()
|
||||||
"String" -> member.string
|
"String" -> member.string
|
||||||
"DateTime" -> DateTime(member.instant.toJavaInstant())
|
"DateTime" -> member.instant?.toJavaInstant()?.let { DateTime(it) }
|
||||||
"Guid" -> member.string?.let { UUID.fromString(it) }
|
"Guid" -> member.string?.let { UUID.fromString(it) }
|
||||||
"ByteString" -> member.value?.list?.let { list ->
|
"ByteString" -> member.value?.list?.let { list ->
|
||||||
ByteString(list.map { it.number.toByte() }.toByteArray())
|
ByteString(list.map { it.number.toByte() }.toByteArray())
|
||||||
|
@ -10,7 +10,7 @@ description = """
|
|||||||
val visionforgeVersion = "0.3.0-dev-10"
|
val visionforgeVersion = "0.3.0-dev-10"
|
||||||
|
|
||||||
kscience {
|
kscience {
|
||||||
fullStack("js/controls-vision.js", development = true)
|
fullStack("js/controls-vision.js")
|
||||||
useKtor()
|
useKtor()
|
||||||
useContextReceivers()
|
useContextReceivers()
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -37,11 +37,11 @@ public fun main() {
|
|||||||
timeStep = 0.005.seconds
|
timeStep = 0.005.seconds
|
||||||
)
|
)
|
||||||
|
|
||||||
val device = context.deviceGroup {
|
val device = context.registerDeviceGroup {
|
||||||
val drive = virtualDrive("drive", 0.005, state)
|
val drive = VirtualDrive(context, 0.005, state)
|
||||||
val pid = pid("pid", drive, pidParameters)
|
val pid = pid("pid", drive, pidParameters)
|
||||||
virtualLimitSwitch("start", state.atStartState)
|
registerDevice("start", LimitSwitch.factory(state.atStartState))
|
||||||
virtualLimitSwitch("end", state.atEndState)
|
registerDevice("end", LimitSwitch.factory(state.atEndState))
|
||||||
|
|
||||||
val clock = context.clock
|
val clock = context.clock
|
||||||
val clockStart = clock.now()
|
val clockStart = clock.now()
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
Loading…
Reference in New Issue
Block a user