diff --git a/build.gradle.kts b/build.gradle.kts index d6ab9cf..6c169fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ val xodusVersion by extra("2.0.1") allprojects { group = "space.kscience" - version = "0.3.0-dev-4" + version = "0.3.0-dev-5" repositories{ maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") } diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt index 01d9087..1eb3b91 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt @@ -2,7 +2,7 @@ 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.Context import space.kscience.dataforge.context.Factory import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.transformations.MetaConverter @@ -18,9 +18,9 @@ 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, + context: Context, meta: Meta, -) : DeviceGroup(deviceManager, meta) { +) : DeviceGroup(context, meta) { /** * Register a device, provided by a given [factory] and diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceGroup.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceGroup.kt index 68f8301..cea9d71 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceGroup.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceGroup.kt @@ -27,7 +27,7 @@ import kotlin.coroutines.CoroutineContext * A mutable group of devices and properties to be used for lightweight design and simulations. */ public open class DeviceGroup( - public val deviceManager: DeviceManager, + final override val context: Context, override val meta: Meta, ) : DeviceHub, CachingDevice { @@ -42,9 +42,6 @@ public open class DeviceGroup( ) - override final val context: Context get() = deviceManager.context - - private val sharedMessageFlow = MutableSharedFlow() override val messageFlow: Flow @@ -175,7 +172,7 @@ public fun DeviceManager.registerDeviceGroup( meta: Meta = Meta.EMPTY, block: DeviceGroup.() -> Unit, ): DeviceGroup { - val group = DeviceGroup(this, meta).apply(block) + val group = DeviceGroup(context, meta).apply(block) install(name, group) return group } @@ -194,7 +191,7 @@ private fun DeviceGroup.getOrCreateGroup(name: Name): DeviceGroup { when (val d = devices[token]) { null -> install( token, - DeviceGroup(deviceManager, meta[token] ?: Meta.EMPTY) + DeviceGroup(context, meta[token] ?: Meta.EMPTY) ) else -> (d as? DeviceGroup) ?: error("Device $name is not a DeviceGroup") @@ -234,7 +231,7 @@ public fun DeviceGroup.install( deviceMeta: Meta? = null, metaLocation: Name = name, ): D { - val newDevice = factory.build(deviceManager.context, Laminate(deviceMeta, meta[metaLocation])) + val newDevice = factory.build(context, Laminate(deviceMeta, meta[metaLocation])) install(name, newDevice) return newDevice } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/phrases.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/phrases.kt index 732e5d4..b86591f 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/phrases.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/phrases.kt @@ -9,6 +9,8 @@ import kotlinx.io.readByteArray /** * Transform byte fragments into complete phrases using given delimiter. Not thread safe. + * + * TODO add type wrapper for phrases */ public fun Flow.withDelimiter(delimiter: ByteArray): Flow { require(delimiter.isNotEmpty()) { "Delimiter must not be empty" } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt index cb511ea..8e0d6de 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt @@ -81,7 +81,7 @@ public suspend fun D.read(propertySpec: DevicePropertySpec public suspend fun > D.readOrNull(propertySpec: DevicePropertySpec): T? = readPropertyOrNull(propertySpec.name)?.let(propertySpec.converter::metaToObject) -public suspend fun D.request(propertySpec: DevicePropertySpec): T? = +public suspend fun D.request(propertySpec: DevicePropertySpec): T = propertySpec.converter.metaToObject(requestProperty(propertySpec.name)) /** diff --git a/controls-vision/src/commonMain/kotlin/plotExtensions.kt b/controls-vision/src/commonMain/kotlin/plotExtensions.kt index 3640ee9..8c7b02f 100644 --- a/controls-vision/src/commonMain/kotlin/plotExtensions.kt +++ b/controls-vision/src/commonMain/kotlin/plotExtensions.kt @@ -18,6 +18,8 @@ import space.kscience.controls.api.propertyMessageFlow import space.kscience.controls.constructor.DeviceState import space.kscience.controls.manager.clock import space.kscience.controls.misc.ValueWithTime +import space.kscience.controls.spec.DevicePropertySpec +import space.kscience.controls.spec.name import space.kscience.dataforge.context.Context import space.kscience.dataforge.meta.* import space.kscience.plotly.Plot @@ -28,8 +30,8 @@ import space.kscience.plotly.models.Trace import space.kscience.plotly.models.TraceValues import space.kscience.plotly.scatter import kotlin.time.Duration -import kotlin.time.Duration.Companion.hours -import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds private var TraceValues.values: List get() = value?.list ?: emptyList() @@ -82,6 +84,11 @@ private class TimeData(private var points: MutableList> = m } } +private val defaultMaxAge get() = 10.minutes +private val defaultMaxPoints get() = 800 +private val defaultMinPoints get() = 400 +private val defaultSampling get() = 1.seconds + /** * Add a trace that shows a [Device] property change over time. Show only latest [maxPoints] . * @return a [Job] that handles the listener @@ -90,10 +97,10 @@ public fun Plot.plotDeviceProperty( device: Device, propertyName: String, extractValue: Meta.() -> Value = { value ?: Null }, - maxAge: Duration = 1.hours, - maxPoints: Int = 800, - minPoints: Int = 400, - sampling: Duration = 10.milliseconds, + maxAge: Duration = defaultMaxAge, + maxPoints: Int = defaultMaxPoints, + minPoints: Int = defaultMinPoints, + sampling: Duration = defaultSampling, coroutineScope: CoroutineScope = device.context, configuration: Scatter.() -> Unit = {}, ): Job = scatter(configuration).run { @@ -108,14 +115,27 @@ public fun Plot.plotDeviceProperty( }.launchIn(coroutineScope) } +public fun Plot.plotDeviceProperty( + device: Device, + property: DevicePropertySpec<*, Number>, + maxAge: Duration = defaultMaxAge, + maxPoints: Int = defaultMaxPoints, + minPoints: Int = defaultMinPoints, + sampling: Duration = defaultSampling, + coroutineScope: CoroutineScope = device.context, + configuration: Scatter.() -> Unit = {}, +): Job = plotDeviceProperty( + device, property.name, { value ?: Null }, maxAge, maxPoints, minPoints, sampling, coroutineScope, configuration +) + private fun Trace.updateFromState( context: Context, state: DeviceState, - extractValue: T.() -> Value = { state.converter.objectToMeta(this).value ?: space.kscience.dataforge.meta.Null }, - maxAge: Duration = 1.hours, - maxPoints: Int = 800, - minPoints: Int = 400, - sampling: Duration = 10.milliseconds, + extractValue: T.() -> Value, + maxAge: Duration, + maxPoints: Int, + minPoints: Int, + sampling: Duration, ): Job { val clock = context.clock val data = TimeData() @@ -131,10 +151,10 @@ public fun Plot.plotDeviceState( context: Context, state: DeviceState, extractValue: T.() -> Value = { state.converter.objectToMeta(this).value ?: Null }, - maxAge: Duration = 1.hours, - maxPoints: Int = 800, - minPoints: Int = 400, - sampling: Duration = 10.milliseconds, + maxAge: Duration = defaultMaxAge, + maxPoints: Int = defaultMaxPoints, + minPoints: Int = defaultMinPoints, + sampling: Duration = defaultSampling, configuration: Scatter.() -> Unit = {}, ): Job = scatter(configuration).run { updateFromState(context, state, extractValue, maxAge, maxPoints, minPoints, sampling) @@ -144,10 +164,10 @@ public fun Plot.plotDeviceState( public fun Plot.plotNumberState( context: Context, state: DeviceState, - maxAge: Duration = 1.hours, - maxPoints: Int = 800, - minPoints: Int = 400, - sampling: Duration = 10.milliseconds, + maxAge: Duration = defaultMaxAge, + maxPoints: Int = defaultMaxPoints, + minPoints: Int = defaultMinPoints, + sampling: Duration = defaultSampling, configuration: Scatter.() -> Unit = {}, ): Job = scatter(configuration).run { updateFromState(context, state, { asValue() }, maxAge, maxPoints, minPoints, sampling) @@ -157,10 +177,10 @@ public fun Plot.plotNumberState( public fun Plot.plotBooleanState( context: Context, state: DeviceState, - maxAge: Duration = 1.hours, - maxPoints: Int = 800, - minPoints: Int = 400, - sampling: Duration = 10.milliseconds, + maxAge: Duration = defaultMaxAge, + maxPoints: Int = defaultMaxPoints, + minPoints: Int = defaultMinPoints, + sampling: Duration = defaultSampling, configuration: Bar.() -> Unit = {}, ): Job = bar(configuration).run { updateFromState(context, state, { asValue() }, maxAge, maxPoints, minPoints, sampling) diff --git a/demo/constructor/src/jvmMain/kotlin/main.kt b/demo/constructor/src/jvmMain/kotlin/main.kt index 3054399..5cbef67 100644 --- a/demo/constructor/src/jvmMain/kotlin/main.kt +++ b/demo/constructor/src/jvmMain/kotlin/main.kt @@ -26,7 +26,6 @@ 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 @@ -44,7 +43,7 @@ class LinearDrive( mass: Double, pidParameters: PidParameters, meta: Meta = Meta.EMPTY, -) : DeviceConstructor(context.request(DeviceManager), meta) { +) : DeviceConstructor(context, meta) { val drive by device(VirtualDrive.factory(mass, state)) val pid by device(PidRegulator(drive, pidParameters))