Test device constructor
This commit is contained in:
parent
825f1a4d04
commit
53fc240c75
@ -33,7 +33,7 @@ public abstract class DeviceConstructor(
|
||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, D>> =
|
||||
PropertyDelegateProvider { _: DeviceConstructor, property: KProperty<*> ->
|
||||
val name = nameOverride ?: property.name.asName()
|
||||
val device = registerDevice(name, factory, meta, metaLocation ?: name)
|
||||
val device = install(name, factory, meta, metaLocation ?: name)
|
||||
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
||||
device
|
||||
}
|
||||
@ -45,7 +45,7 @@ public abstract class DeviceConstructor(
|
||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, D>> =
|
||||
PropertyDelegateProvider { _: DeviceConstructor, property: KProperty<*> ->
|
||||
val name = nameOverride ?: property.name.asName()
|
||||
registerDevice(name, device)
|
||||
install(name, device)
|
||||
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
||||
device
|
||||
}
|
||||
@ -58,7 +58,7 @@ public abstract class DeviceConstructor(
|
||||
public fun <T : Any> property(
|
||||
state: DeviceState<T>,
|
||||
nameOverride: String? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> =
|
||||
PropertyDelegateProvider { _: DeviceConstructor, property ->
|
||||
val name = nameOverride ?: property.name
|
||||
@ -78,7 +78,7 @@ public abstract class DeviceConstructor(
|
||||
readInterval: Duration,
|
||||
initialState: T,
|
||||
nameOverride: String? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> = property(
|
||||
DeviceState.external(this, metaConverter, readInterval, initialState, reader),
|
||||
nameOverride, descriptorBuilder
|
||||
@ -91,8 +91,8 @@ public abstract class DeviceConstructor(
|
||||
public fun <T : Any> mutableProperty(
|
||||
state: MutableDeviceState<T>,
|
||||
nameOverride: String? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit,
|
||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> =
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceConstructor, ReadWriteProperty<DeviceConstructor, T>> =
|
||||
PropertyDelegateProvider { _: DeviceConstructor, property ->
|
||||
val name = nameOverride ?: property.name
|
||||
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
|
||||
@ -117,8 +117,8 @@ public abstract class DeviceConstructor(
|
||||
readInterval: Duration,
|
||||
initialState: T,
|
||||
nameOverride: String? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit,
|
||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> = mutableProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceConstructor, ReadWriteProperty<DeviceConstructor, T>> = mutableProperty(
|
||||
DeviceState.external(this, metaConverter, readInterval, initialState, reader, writer),
|
||||
nameOverride,
|
||||
descriptorBuilder
|
||||
|
@ -3,6 +3,8 @@ package space.kscience.controls.constructor
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import space.kscience.controls.api.*
|
||||
import space.kscience.controls.api.DeviceLifecycleState.*
|
||||
import space.kscience.controls.manager.DeviceManager
|
||||
@ -40,25 +42,30 @@ public open class DeviceGroup(
|
||||
)
|
||||
|
||||
|
||||
override val context: Context get() = deviceManager.context
|
||||
override final val context: Context get() = deviceManager.context
|
||||
|
||||
override val coroutineContext: CoroutineContext by lazy {
|
||||
context.newCoroutineContext(
|
||||
SupervisorJob(context.coroutineContext[Job]) +
|
||||
CoroutineName("Device $this") +
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
launch {
|
||||
sharedMessageFlow.emit(
|
||||
DeviceErrorMessage(
|
||||
errorMessage = throwable.message,
|
||||
errorType = throwable::class.simpleName,
|
||||
errorStackTrace = throwable.stackTraceToString()
|
||||
)
|
||||
|
||||
private val sharedMessageFlow = MutableSharedFlow<DeviceMessage>()
|
||||
|
||||
override val messageFlow: Flow<DeviceMessage>
|
||||
get() = sharedMessageFlow
|
||||
|
||||
override val coroutineContext: CoroutineContext = context.newCoroutineContext(
|
||||
SupervisorJob(context.coroutineContext[Job]) +
|
||||
CoroutineName("Device $this") +
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
context.launch {
|
||||
sharedMessageFlow.emit(
|
||||
DeviceErrorMessage(
|
||||
errorMessage = throwable.message,
|
||||
errorType = throwable::class.simpleName,
|
||||
errorStackTrace = throwable.stackTraceToString()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
private val _devices = hashMapOf<NameToken, Device>()
|
||||
|
||||
@ -68,14 +75,10 @@ public open class DeviceGroup(
|
||||
* 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 {
|
||||
public fun <D : Device> install(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 -> {}
|
||||
}
|
||||
//start the child device if needed
|
||||
if(lifecycleState == STARTED || lifecycleState == STARTING) launch { device.start() }
|
||||
_devices[token] = device
|
||||
return device
|
||||
}
|
||||
@ -89,6 +92,14 @@ public open class DeviceGroup(
|
||||
val name = descriptor.name.parseAsName()
|
||||
require(properties[name] == null) { "Can't add property with name $name. It already exists." }
|
||||
properties[name] = Property(state, descriptor)
|
||||
state.metaFlow.onEach {
|
||||
sharedMessageFlow.emit(
|
||||
PropertyChangedMessage(
|
||||
descriptor.name,
|
||||
it
|
||||
)
|
||||
)
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
private val actions: MutableMap<Name, Action> = hashMapOf()
|
||||
@ -115,10 +126,6 @@ public open class DeviceGroup(
|
||||
property.valueAsMeta = value
|
||||
}
|
||||
|
||||
private val sharedMessageFlow = MutableSharedFlow<DeviceMessage>()
|
||||
|
||||
override val messageFlow: Flow<DeviceMessage>
|
||||
get() = sharedMessageFlow
|
||||
|
||||
override suspend fun execute(actionName: String, argument: Meta?): Meta? {
|
||||
val action = actions[actionName] ?: error("Action with name $actionName not found")
|
||||
@ -185,7 +192,7 @@ private fun DeviceGroup.getOrCreateGroup(name: Name): DeviceGroup {
|
||||
1 -> {
|
||||
val token = name.first()
|
||||
when (val d = devices[token]) {
|
||||
null -> registerDevice(
|
||||
null -> install(
|
||||
token,
|
||||
DeviceGroup(deviceManager, meta[token] ?: Meta.EMPTY)
|
||||
)
|
||||
@ -201,15 +208,18 @@ private fun DeviceGroup.getOrCreateGroup(name: Name): DeviceGroup {
|
||||
/**
|
||||
* Register a device at given [name] path
|
||||
*/
|
||||
public fun <D : Device> DeviceGroup.registerDevice(name: Name, device: D): D {
|
||||
public fun <D : Device> DeviceGroup.install(name: Name, device: D): D {
|
||||
return when (name.length) {
|
||||
0 -> error("Can't use empty name for a child device")
|
||||
1 -> registerDevice(name.first(), device)
|
||||
else -> getOrCreateGroup(name.cutLast()).registerDevice(name.tokens.last(), device)
|
||||
1 -> install(name.first(), device)
|
||||
else -> getOrCreateGroup(name.cutLast()).install(name.tokens.last(), device)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <D : Device> DeviceGroup.registerDevice(name: String, device: D): D = registerDevice(name.parseAsName(), device)
|
||||
public fun <D : Device> DeviceGroup.install(name: String, device: D): D =
|
||||
install(name.parseAsName(), device)
|
||||
|
||||
public fun <D : Device> Context.install(name: String, device: D): D = request(DeviceManager).install(name, device)
|
||||
|
||||
/**
|
||||
* Add a device creating intermediate groups if necessary. If device with given [name] already exists, throws an error.
|
||||
@ -218,23 +228,23 @@ public fun <D : Device> DeviceGroup.registerDevice(name: String, device: D): D =
|
||||
* @param deviceMeta meta override for this specific device
|
||||
* @param metaLocation location of the template meta in parent group meta
|
||||
*/
|
||||
public fun <D : Device> DeviceGroup.registerDevice(
|
||||
public fun <D : Device> DeviceGroup.install(
|
||||
name: Name,
|
||||
factory: Factory<D>,
|
||||
deviceMeta: Meta? = null,
|
||||
metaLocation: Name = name,
|
||||
): D {
|
||||
val newDevice = factory.build(deviceManager.context, Laminate(deviceMeta, meta[metaLocation]))
|
||||
registerDevice(name, newDevice)
|
||||
install(name, newDevice)
|
||||
return newDevice
|
||||
}
|
||||
|
||||
public fun <D : Device> DeviceGroup.registerDevice(
|
||||
public fun <D : Device> DeviceGroup.install(
|
||||
name: String,
|
||||
factory: Factory<D>,
|
||||
metaLocation: Name = name.parseAsName(),
|
||||
metaBuilder: (MutableMeta.() -> Unit)? = null,
|
||||
): D = registerDevice(name.parseAsName(), factory, metaBuilder?.let { Meta(it) }, metaLocation)
|
||||
): D = install(name.parseAsName(), factory, metaBuilder?.let { Meta(it) }, metaLocation)
|
||||
|
||||
/**
|
||||
* Create or edit a group with a given [name].
|
||||
|
@ -75,7 +75,7 @@ private open class BoundDeviceState<T>(
|
||||
/**
|
||||
* Bind a read-only [DeviceState] to a [Device] property
|
||||
*/
|
||||
public suspend fun <T> Device.bindStateToProperty(
|
||||
public suspend fun <T> Device.propertyAsState(
|
||||
propertyName: String,
|
||||
metaConverter: MetaConverter<T>,
|
||||
): DeviceState<T> {
|
||||
@ -83,9 +83,9 @@ public suspend fun <T> Device.bindStateToProperty(
|
||||
return BoundDeviceState(metaConverter, this, propertyName, initialValue)
|
||||
}
|
||||
|
||||
public suspend fun <D : Device, T> D.bindStateToProperty(
|
||||
public suspend fun <D : Device, T> D.propertyAsState(
|
||||
propertySpec: DevicePropertySpec<D, T>,
|
||||
): DeviceState<T> = bindStateToProperty(propertySpec.name, propertySpec.converter)
|
||||
): DeviceState<T> = propertyAsState(propertySpec.name, propertySpec.converter)
|
||||
|
||||
public fun <T, R> DeviceState<T>.map(
|
||||
converter: MetaConverter<R>, mapper: (T) -> R,
|
||||
@ -113,17 +113,28 @@ private class MutableBoundDeviceState<T>(
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun <T> Device.bindMutableStateToProperty(
|
||||
public fun <T> Device.mutablePropertyAsState(
|
||||
propertyName: String,
|
||||
metaConverter: MetaConverter<T>,
|
||||
initialValue: T,
|
||||
): MutableDeviceState<T> = MutableBoundDeviceState(metaConverter, this, propertyName, initialValue)
|
||||
|
||||
public suspend fun <T> Device.mutablePropertyAsState(
|
||||
propertyName: String,
|
||||
metaConverter: MetaConverter<T>,
|
||||
): MutableDeviceState<T> {
|
||||
val initialValue = metaConverter.metaToObject(readProperty(propertyName)) ?: error("Conversion of property failed")
|
||||
return MutableBoundDeviceState(metaConverter, this, propertyName, initialValue)
|
||||
return mutablePropertyAsState(propertyName, metaConverter, initialValue)
|
||||
}
|
||||
|
||||
public suspend fun <D : Device, T> D.bindMutableStateToProperty(
|
||||
public suspend fun <D : Device, T> D.mutablePropertyAsState(
|
||||
propertySpec: MutableDevicePropertySpec<D, T>,
|
||||
): MutableDeviceState<T> = bindMutableStateToProperty(propertySpec.name, propertySpec.converter)
|
||||
): MutableDeviceState<T> = mutablePropertyAsState(propertySpec.name, propertySpec.converter)
|
||||
|
||||
public fun <D : Device, T> D.mutablePropertyAsState(
|
||||
propertySpec: MutableDevicePropertySpec<D, T>,
|
||||
initialValue: T,
|
||||
): MutableDeviceState<T> = mutablePropertyAsState(propertySpec.name, propertySpec.converter, initialValue)
|
||||
|
||||
|
||||
private open class ExternalState<T>(
|
||||
|
@ -96,4 +96,4 @@ public class VirtualDrive(
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun Drive.stateOfForce(): MutableDeviceState<Double> = bindMutableStateToProperty(Drive.force)
|
||||
public suspend fun Drive.stateOfForce(): MutableDeviceState<Double> = mutablePropertyAsState(Drive.force)
|
||||
|
@ -79,4 +79,4 @@ public fun DeviceGroup.pid(
|
||||
name: String,
|
||||
drive: Drive,
|
||||
pidParameters: PidParameters,
|
||||
): PidRegulator = registerDevice(name, PidRegulator(drive, pidParameters))
|
||||
): PidRegulator = install(name, PidRegulator(drive, pidParameters))
|
@ -72,23 +72,21 @@ public abstract class DeviceBase<D : Device>(
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
|
||||
override val coroutineContext: CoroutineContext by lazy {
|
||||
context.newCoroutineContext(
|
||||
SupervisorJob(context.coroutineContext[Job]) +
|
||||
CoroutineName("Device $this") +
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
launch {
|
||||
sharedMessageFlow.emit(
|
||||
DeviceErrorMessage(
|
||||
errorMessage = throwable.message,
|
||||
errorType = throwable::class.simpleName,
|
||||
errorStackTrace = throwable.stackTraceToString()
|
||||
)
|
||||
override val coroutineContext: CoroutineContext = context.newCoroutineContext(
|
||||
SupervisorJob(context.coroutineContext[Job]) +
|
||||
CoroutineName("Device $this") +
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
launch {
|
||||
sharedMessageFlow.emit(
|
||||
DeviceErrorMessage(
|
||||
errorMessage = throwable.message,
|
||||
errorType = throwable::class.simpleName,
|
||||
errorStackTrace = throwable.stackTraceToString()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1,3 +1,5 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
application
|
||||
@ -20,4 +22,6 @@ kscience {
|
||||
|
||||
application {
|
||||
mainClass.set("space.kscience.controls.demo.constructor.MainKt")
|
||||
}
|
||||
}
|
||||
|
||||
kotlin.explicitApi = ExplicitApiMode.Disabled
|
@ -1,18 +1,18 @@
|
||||
package space.kscience.controls.demo.constructor
|
||||
|
||||
import space.kscience.controls.api.get
|
||||
import space.kscience.controls.constructor.*
|
||||
import space.kscience.controls.manager.ClockManager
|
||||
import space.kscience.controls.manager.DeviceManager
|
||||
import space.kscience.controls.manager.clock
|
||||
import space.kscience.controls.spec.doRecurring
|
||||
import space.kscience.controls.spec.name
|
||||
import space.kscience.controls.spec.write
|
||||
import space.kscience.controls.vision.plot
|
||||
import space.kscience.controls.vision.plotDeviceProperty
|
||||
import space.kscience.controls.vision.plotNumberState
|
||||
import space.kscience.controls.vision.showDashboard
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.plotly.models.ScatterMode
|
||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||
import kotlin.math.PI
|
||||
@ -21,6 +21,27 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.DurationUnit
|
||||
|
||||
|
||||
class LinearDrive(
|
||||
context: Context,
|
||||
state: DoubleRangeState,
|
||||
mass: Double,
|
||||
pidParameters: PidParameters,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : DeviceConstructor(context.request(DeviceManager), meta) {
|
||||
|
||||
val drive by device(VirtualDrive.factory(mass, state))
|
||||
val pid by device(PidRegulator(drive, pidParameters))
|
||||
|
||||
val start by device(LimitSwitch.factory(state.atStartState))
|
||||
val end by device(LimitSwitch.factory(state.atEndState))
|
||||
|
||||
|
||||
val position by property(state)
|
||||
var target by mutableProperty(pid.mutablePropertyAsState(Regulator.target, 0.0))
|
||||
}
|
||||
|
||||
|
||||
public fun main() {
|
||||
val context = Context {
|
||||
plugin(DeviceManager)
|
||||
@ -37,25 +58,20 @@ public fun main() {
|
||||
timeStep = 0.005.seconds
|
||||
)
|
||||
|
||||
val device = context.registerDeviceGroup {
|
||||
val drive = VirtualDrive(context, 0.005, state)
|
||||
val pid = pid("pid", drive, pidParameters)
|
||||
registerDevice("start", LimitSwitch.factory(state.atStartState))
|
||||
registerDevice("end", LimitSwitch.factory(state.atEndState))
|
||||
|
||||
val device = context.install("device", LinearDrive(context, state, 0.005, pidParameters)).apply {
|
||||
val clock = context.clock
|
||||
val clockStart = clock.now()
|
||||
|
||||
doRecurring(10.milliseconds) {
|
||||
val timeFromStart = clock.now() - clockStart
|
||||
val t = timeFromStart.toDouble(DurationUnit.SECONDS)
|
||||
val freq = 0.1
|
||||
val target = 5 * sin(2.0 * PI * freq * t) +
|
||||
|
||||
target = 5 * sin(2.0 * PI * freq * t) +
|
||||
sin(2 * PI * 21 * freq * t + 0.02 * (timeFromStart / pidParameters.timeStep))
|
||||
pid.write(Regulator.target, target)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val maxAge = 10.seconds
|
||||
|
||||
context.showDashboard {
|
||||
@ -63,21 +79,21 @@ public fun main() {
|
||||
plotNumberState(context, state, maxAge = maxAge) {
|
||||
name = "real position"
|
||||
}
|
||||
plotDeviceProperty(device["pid"], Regulator.position.name, maxAge = maxAge) {
|
||||
plotDeviceProperty(device.pid, Regulator.position.name, maxAge = maxAge) {
|
||||
name = "read position"
|
||||
}
|
||||
|
||||
plotDeviceProperty(device["pid"], Regulator.target.name, maxAge = maxAge) {
|
||||
plotDeviceProperty(device.pid, Regulator.target.name, maxAge = maxAge) {
|
||||
name = "target"
|
||||
}
|
||||
}
|
||||
|
||||
plot {
|
||||
plotDeviceProperty(device["start"], LimitSwitch.locked.name, maxAge = maxAge) {
|
||||
plotDeviceProperty(device.start, LimitSwitch.locked.name, maxAge = maxAge) {
|
||||
name = "start measured"
|
||||
mode = ScatterMode.markers
|
||||
}
|
||||
plotDeviceProperty(device["end"], LimitSwitch.locked.name, maxAge = maxAge) {
|
||||
plotDeviceProperty(device.end, LimitSwitch.locked.name, maxAge = maxAge) {
|
||||
name = "end measured"
|
||||
mode = ScatterMode.markers
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user