From 54e915ef108b37f89092113334117f75db488f71 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 1 Jun 2024 09:35:03 +0300 Subject: [PATCH] WIP Constructor update --- .../constructor/ConstructorElement.kt | 7 +- .../controls/constructor/DeviceConstructor.kt | 18 ++-- .../controls/constructor/DeviceGroup.kt | 17 ++-- .../controls/constructor/DeviceState.kt | 4 +- ...onstructorModel.kt => ModelConstructor.kt} | 2 +- .../controls/constructor/exoticState.kt | 57 ------------- .../controls/constructor/internalState.kt | 16 ++++ .../controls/constructor/library/Converter.kt | 20 ----- .../constructor/library/DoubleInRangeState.kt | 57 +++++++++++++ .../controls/constructor/library/Drive.kt | 3 +- .../constructor/library/LimitSwitch.kt | 48 ++++++----- .../constructor/library/PidRegulator.kt | 83 ++++++++----------- .../controls/constructor/library/Regulator.kt | 24 ++---- .../controls/constructor/library/StepDrive.kt | 33 ++++++++ .../constructor/library/Transmission.kt | 33 ++++++++ .../controls/constructor/units/Direction.kt | 6 ++ .../constructor/units/NumericalValue.kt | 29 ++++++- .../constructor/units/UnitsOfMeasurement.kt | 23 +++-- .../controls/constructor/TimerTest.kt | 3 +- .../kscience/controls/manager/ClockManager.kt | 9 ++ .../src/jvmMain/kotlin/BodyOnSprings.kt | 4 +- .../kotlin/{LinearDrive.kt => PidDemo.kt} | 36 ++++---- 22 files changed, 320 insertions(+), 212 deletions(-) rename controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/{ConstructorModel.kt => ModelConstructor.kt} (96%) delete mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/exoticState.kt delete mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Converter.kt create mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/DoubleInRangeState.kt create mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/StepDrive.kt create mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Transmission.kt create mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/Direction.kt rename demo/constructor/src/jvmMain/kotlin/{LinearDrive.kt => PidDemo.kt} (92%) diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorElement.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorElement.kt index 911a682..0aa72a0 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorElement.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorElement.kt @@ -36,7 +36,7 @@ public class ConnectionConstrucorElement( ) : ConstructorElement public class ModelConstructorElement( - public val model: ConstructorModel + public val model: ModelConstructor ) : ConstructorElement @@ -89,7 +89,7 @@ public fun StateContainer.stateOf(initialValue: T): MutableDeviceState = MutableDeviceState(initialValue) ) -public fun StateContainer.model(model: T): T { +public fun StateContainer.model(model: T): T { registerElement(ModelConstructorElement(model)) return model } @@ -125,14 +125,13 @@ public fun StateContainer.combineState( transformation: (T1, T2) -> R, ): DeviceState = state(DeviceState.combine(first, second, transformation)) - /** * Create and start binding between [sourceState] and [targetState]. Changes made to [sourceState] are automatically * transferred onto [targetState], but not vise versa. * * On resulting [Job] cancel the binding is unregistered */ -public fun StateContainer.bindTo(sourceState: DeviceState, targetState: MutableDeviceState): Job { +public fun StateContainer.bind(sourceState: DeviceState, targetState: MutableDeviceState): Job { val descriptor = ConnectionConstrucorElement(setOf(sourceState), setOf(targetState)) registerElement(descriptor) return sourceState.valueFlow.onEach { 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 8854247..00d408e 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 @@ -32,13 +32,14 @@ public abstract class DeviceConstructor( _constructorElements.remove(constructorElement) } - override fun registerProperty( + override fun > registerAsProperty( converter: MetaConverter, descriptor: PropertyDescriptor, - state: DeviceState, - ) { - super.registerProperty(converter, descriptor, state) + state: S, + ): S { + val res = super.registerAsProperty(converter, descriptor, state) registerElement(PropertyConstructorElement(this, descriptor.name, state)) + return res } } @@ -83,7 +84,7 @@ public fun > DeviceConstructor.property( PropertyDelegateProvider { _: DeviceConstructor, property -> val name = nameOverride ?: property.name val descriptor = PropertyDescriptor(name).apply(descriptorBuilder) - registerProperty(converter, descriptor, state) + registerAsProperty(converter, descriptor, state) ReadOnlyProperty { _: DeviceConstructor, _ -> state } @@ -140,7 +141,10 @@ public fun DeviceConstructor.virtualProperty( nameOverride, ) -public fun > DeviceConstructor.property( +public fun > DeviceConstructor.registerAsProperty( spec: DevicePropertySpec<*, T>, state: S, -): Unit = registerProperty(spec.converter, spec.descriptor, state) +): S { + registerAsProperty(spec.converter, spec.descriptor, state) + return state +} 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 ddac115..182d10b 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 @@ -93,11 +93,11 @@ public open class DeviceGroup( /** * Register a new property based on [DeviceState]. Properties could be modified dynamically */ - public open fun registerProperty( + public open fun > registerAsProperty( converter: MetaConverter, descriptor: PropertyDescriptor, - state: DeviceState, - ) { + state: S, + ): S { val name = descriptor.name.parseAsName() require(properties[name] == null) { "Can't add property with name $name. It already exists." } properties[name] = Property(state, converter, descriptor) @@ -109,6 +109,7 @@ public open class DeviceGroup( ) ) }.launchIn(this) + return state } private val actions: MutableMap = hashMapOf() @@ -174,8 +175,8 @@ public open class DeviceGroup( } } -public fun DeviceGroup.registerProperty(propertySpec: DevicePropertySpec<*, T>, state: DeviceState) { - registerProperty(propertySpec.converter, propertySpec.descriptor, state) +public fun DeviceGroup.registerAsProperty(propertySpec: DevicePropertySpec<*, T>, state: DeviceState) { + registerAsProperty(propertySpec.converter, propertySpec.descriptor, state) } public fun DeviceManager.registerDeviceGroup( @@ -246,13 +247,13 @@ public fun DeviceGroup.registerDeviceGroup(name: String, block: DeviceGroup.() - /** * Register read-only property based on [state] */ -public fun DeviceGroup.registerProperty( +public fun DeviceGroup.registerAsProperty( name: String, converter: MetaConverter, state: DeviceState, descriptorBuilder: PropertyDescriptor.() -> Unit = {}, ) { - registerProperty( + registerAsProperty( converter, PropertyDescriptor(name).apply(descriptorBuilder), state @@ -268,7 +269,7 @@ public fun DeviceGroup.registerMutableProperty( state: MutableDeviceState, descriptorBuilder: PropertyDescriptor.() -> Unit = {}, ) { - registerProperty( + registerAsProperty( converter, PropertyDescriptor(name).apply(descriptorBuilder), state diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceState.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceState.kt index db388d3..5083347 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceState.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceState.kt @@ -67,9 +67,11 @@ public fun DeviceState.Companion.map( override val valueFlow: Flow = state.valueFlow.map(mapper) - override fun toString(): String = "DeviceState.map(arg=${state})" + override fun toString(): String = "DeviceState.map(state=${state})" } +public fun DeviceState.map(mapper: (T) -> R): DeviceStateWithDependencies = DeviceState.map(this, mapper) + /** * Combine two device states into one read-only [DeviceState]. Only the latest value of each state is used. */ diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorModel.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ModelConstructor.kt similarity index 96% rename from controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorModel.kt rename to controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ModelConstructor.kt index 35a1e31..8fdc708 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorModel.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ModelConstructor.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.newCoroutineContext import space.kscience.dataforge.context.Context import kotlin.coroutines.CoroutineContext -public abstract class ConstructorModel( +public abstract class ModelConstructor( final override val context: Context, vararg dependencies: DeviceState<*>, ) : StateContainer, CoroutineScope { diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/exoticState.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/exoticState.kt deleted file mode 100644 index 96ea4fe..0000000 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/exoticState.kt +++ /dev/null @@ -1,57 +0,0 @@ -package space.kscience.controls.constructor - -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow - - -/** - * A state describing a [Double] value in the [range] - */ -public class DoubleInRangeState( - initialValue: Double, - public val range: ClosedFloatingPointRange, -) : MutableDeviceState { - - init { - require(initialValue in range) { "Initial value should be in range" } - } - - - private val _valueFlow = MutableStateFlow(initialValue) - - override var value: Double - get() = _valueFlow.value - set(newValue) { - _valueFlow.value = newValue.coerceIn(range) - } - - override val valueFlow: StateFlow get() = _valueFlow - - /** - * A state showing that the range is on its lower boundary - */ - public val atStart: DeviceState = DeviceState.map(this) { - it <= range.start - } - - /** - * A state showing that the range is on its higher boundary - */ - public val atEnd: DeviceState = DeviceState.map(this) { - it >= range.endInclusive - } - - override fun toString(): String = "DoubleRangeState(range=$range)" - - -} - -/** - * Create and register a [DoubleInRangeState] - */ -public fun StateContainer.doubleInRangeState( - initialValue: Double, - range: ClosedFloatingPointRange, -): DoubleInRangeState = DoubleInRangeState(initialValue, range).also { - registerElement(StateConstructorElement(it)) -} \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/internalState.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/internalState.kt index 11c778d..8bdfce1 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/internalState.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/internalState.kt @@ -2,6 +2,7 @@ package space.kscience.controls.constructor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow /** * A [MutableDeviceState] that does not correspond to a physical state @@ -35,3 +36,18 @@ public fun MutableDeviceState( initialValue: T, callback: (T) -> Unit = {}, ): MutableDeviceState = VirtualDeviceState(initialValue, callback) + + +/** + * Create a [DeviceState] with constant value + */ +public fun DeviceState( + value: T +): DeviceState = object : DeviceState { + override val value: T get() = value + override val valueFlow: Flow + get() = emptyFlow() + + override fun toString(): String = "ConstDeviceState($value)" + +} \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Converter.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Converter.kt deleted file mode 100644 index 45cd4db..0000000 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Converter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package space.kscience.controls.constructor.library - -import kotlinx.coroutines.flow.FlowCollector -import space.kscience.controls.constructor.DeviceConstructor -import space.kscience.controls.constructor.DeviceState -import space.kscience.controls.constructor.DeviceStateWithDependencies -import space.kscience.controls.constructor.flowState -import space.kscience.dataforge.context.Context - -/** - * A device that converts one type of physical quantity to another type - */ -public class Converter( - context: Context, - input: DeviceState, - initialValue: R, - transform: suspend FlowCollector.(T) -> Unit, -) : DeviceConstructor(context) { - public val output: DeviceStateWithDependencies = flowState(input, initialValue, transform) -} \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/DoubleInRangeState.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/DoubleInRangeState.kt new file mode 100644 index 0000000..10773da --- /dev/null +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/DoubleInRangeState.kt @@ -0,0 +1,57 @@ +package space.kscience.controls.constructor.library + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import space.kscience.controls.constructor.DeviceState +import space.kscience.controls.constructor.MutableDeviceState +import space.kscience.controls.constructor.map + +/** + * A state describing a [Double] value in the [range] + */ +public open class DoubleInRangeState( + private val input: DeviceState, + public val range: ClosedFloatingPointRange, +) : DeviceState { + + override val valueFlow: Flow get() = input.valueFlow.map { it.coerceIn(range) } + + override val value: Double get() = input.value.coerceIn(range) + + /** + * A state showing that the range is on its lower boundary + */ + public val atStart: DeviceState = input.map { it <= range.start } + + /** + * A state showing that the range is on its higher boundary + */ + public val atEnd: DeviceState = input.map { it >= range.endInclusive } + + override fun toString(): String = "DoubleRangeState(value=${value},range=$range)" +} + +public class MutableDoubleInRangeState( + private val mutableInput: MutableDeviceState, + range: ClosedFloatingPointRange +) : DoubleInRangeState(mutableInput, range), MutableDeviceState { + override var value: Double + get() = super.value + set(value) { + mutableInput.value = value.coerceIn(range) + } +} + +public fun MutableDoubleInRangeState( + initialValue: Double, + range: ClosedFloatingPointRange +): MutableDoubleInRangeState = MutableDoubleInRangeState(MutableDeviceState(initialValue),range) + + +public fun DeviceState.coerceIn( + range: ClosedFloatingPointRange +): DoubleInRangeState = DoubleInRangeState(this, range) + +public fun MutableDeviceState.coerceIn( + range: ClosedFloatingPointRange +): MutableDoubleInRangeState = MutableDoubleInRangeState(this, range) \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Drive.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Drive.kt index 6a07db0..77a16ca 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Drive.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Drive.kt @@ -98,4 +98,5 @@ public class VirtualDrive( } } -public suspend fun Drive.stateOfForce(): MutableDeviceState = propertyAsState(Drive.force) +public fun Drive.stateOfForce(initialForce: Double = 0.0): MutableDeviceState = + propertyAsState(Drive.force, initialForce) diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/LimitSwitch.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/LimitSwitch.kt index 876d2dc..919d026 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/LimitSwitch.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/LimitSwitch.kt @@ -1,37 +1,45 @@ package space.kscience.controls.constructor.library -import space.kscience.controls.api.Device import space.kscience.controls.constructor.DeviceConstructor import space.kscience.controls.constructor.DeviceState -import space.kscience.controls.constructor.property +import space.kscience.controls.constructor.map +import space.kscience.controls.constructor.registerAsProperty +import space.kscience.controls.constructor.units.Direction +import space.kscience.controls.constructor.units.NumericalValue +import space.kscience.controls.constructor.units.UnitsOfMeasurement +import space.kscience.controls.constructor.units.compareTo import space.kscience.controls.spec.DevicePropertySpec import space.kscience.controls.spec.DeviceSpec import space.kscience.controls.spec.booleanProperty import space.kscience.dataforge.context.Context -import space.kscience.dataforge.meta.MetaConverter -/** - * A limit switch device - */ -public interface LimitSwitch : Device { - - public fun isLocked(): Boolean - - public companion object : DeviceSpec() { - public val locked: DevicePropertySpec by booleanProperty { isLocked() } - } -} - /** * Virtual [LimitSwitch] */ -public class VirtualLimitSwitch( +public class LimitSwitch( context: Context, locked: DeviceState, -) : DeviceConstructor(context), LimitSwitch { +) : DeviceConstructor(context) { - public val locked: DeviceState by property(MetaConverter.boolean, locked) + public val locked: DeviceState = registerAsProperty(LimitSwitch.locked, locked) - override fun isLocked(): Boolean = locked.value -} \ No newline at end of file + public companion object : DeviceSpec() { + public val locked: DevicePropertySpec by booleanProperty { locked.value } + } +} + +public fun > LimitSwitch( + context: Context, + limit: T, + boundary: Direction, + position: DeviceState, +): LimitSwitch = LimitSwitch( + context, + DeviceState.map(position) { + when (boundary) { + Direction.UP -> it >= limit + Direction.DOWN -> it <= limit + } + } +) \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/PidRegulator.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/PidRegulator.kt index 9edf91e..27c3f95 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/PidRegulator.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/PidRegulator.kt @@ -1,19 +1,17 @@ package space.kscience.controls.constructor.library -import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import kotlinx.datetime.Instant -import space.kscience.controls.constructor.DeviceGroup +import space.kscience.controls.constructor.DeviceState +import space.kscience.controls.constructor.MutableDeviceState +import space.kscience.controls.constructor.state +import space.kscience.controls.constructor.stateOf import space.kscience.controls.manager.clock -import space.kscience.controls.spec.DeviceBySpec -import space.kscience.controls.spec.write -import space.kscience.dataforge.names.parseAsName +import space.kscience.dataforge.context.Context import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds import kotlin.time.DurationUnit @@ -24,64 +22,53 @@ public data class PidParameters( val kp: Double, val ki: Double, val kd: Double, - val timeStep: Duration = 1.milliseconds, + val timeStep: Duration, ) + /** - * A drive with PID regulator + * A PID regulator */ public class PidRegulator( - public val drive: Drive, + context: Context, + private val position: DeviceState, public var pidParameters: PidParameters, // TODO expose as property -) : DeviceBySpec(Regulator, drive.context), Regulator { + output: MutableDeviceState = MutableDeviceState(0.0), +) : Regulator(context) { - private val clock = drive.context.clock + override val target: MutableDeviceState = stateOf(0.0) + override val output: MutableDeviceState = state(output) - override var target: Double = drive.position - - private var lastTime: Instant = clock.now() - private var lastPosition: Double = target + private var lastPosition: Double = target.value private var integral: Double = 0.0 - - private var updateJob: Job? = null private val mutex = Mutex() + private var lastTime = clock.now() - override suspend fun onStart() { - drive.start() - updateJob = launch { - while (isActive) { - delay(pidParameters.timeStep) - mutex.withLock { - val realTime = clock.now() - val delta = target - getPosition() - val dtSeconds = (realTime - lastTime).toDouble(DurationUnit.SECONDS) - integral += delta * dtSeconds - val derivative = (drive.position - lastPosition) / dtSeconds + private val updateJob = launch { + while (isActive) { + delay(pidParameters.timeStep) + mutex.withLock { + val realTime = clock.now() + val delta = target.value - position.value + val dtSeconds = (realTime - lastTime).toDouble(DurationUnit.SECONDS) + integral += delta * dtSeconds + val derivative = (position.value - lastPosition) / dtSeconds - //set last time and value to new values - lastTime = realTime - lastPosition = drive.position + //set last time and value to new values + lastTime = realTime + lastPosition = position.value - drive.write(Drive.force,pidParameters.kp * delta + pidParameters.ki * integral + pidParameters.kd * derivative) - //drive.force = pidParameters.kp * delta + pidParameters.ki * integral + pidParameters.kd * derivative - propertyChanged(Regulator.position, drive.position) - } + output.value = pidParameters.kp * delta + pidParameters.ki * integral + pidParameters.kd * derivative } } } - - override suspend fun onStop() { - updateJob?.cancel() - drive.stop() - } - - override suspend fun getPosition(): Double = drive.position } -public fun DeviceGroup.pid( - name: String, - drive: Drive, - pidParameters: PidParameters, -): PidRegulator = install(name.parseAsName(), PidRegulator(drive, pidParameters)) \ No newline at end of file +// +//public fun DeviceGroup.pid( +// name: String, +// drive: Drive, +// pidParameters: PidParameters, +//): PidRegulator = install(name.parseAsName(), PidRegulator(drive, pidParameters)) \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Regulator.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Regulator.kt index eb9a333..628fb0e 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Regulator.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Regulator.kt @@ -1,27 +1,17 @@ package space.kscience.controls.constructor.library -import space.kscience.controls.api.Device -import space.kscience.controls.spec.* -import space.kscience.dataforge.meta.MetaConverter +import space.kscience.controls.constructor.DeviceConstructor +import space.kscience.controls.constructor.DeviceState +import space.kscience.controls.constructor.MutableDeviceState +import space.kscience.dataforge.context.Context /** * A regulator with target value and current position */ -public interface Regulator : Device { - /** - * Get or set target value - */ - public var target: Double +public abstract class Regulator(context: Context) : DeviceConstructor(context) { - /** - * Current position value - */ - public suspend fun getPosition(): Double + public abstract val target: MutableDeviceState - public companion object : DeviceSpec() { - public val target: MutableDevicePropertySpec by mutableProperty(MetaConverter.double, Regulator::target) - - public val position: DevicePropertySpec by doubleProperty { getPosition() } - } + public abstract val output: DeviceState } \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/StepDrive.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/StepDrive.kt new file mode 100644 index 0000000..47a9742 --- /dev/null +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/StepDrive.kt @@ -0,0 +1,33 @@ +package space.kscience.controls.constructor.library + +import space.kscience.controls.constructor.DeviceState +import space.kscience.controls.constructor.MutableDeviceState +import space.kscience.controls.constructor.combineState +import space.kscience.controls.constructor.property +import space.kscience.controls.constructor.units.* +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.meta.MetaConverter + +/** + * A step drive regulated by [input] + */ +public class StepDrive( + context: Context, + public val step: NumericalValue, + public val zero: NumericalValue = NumericalValue(0.0), + direction: Direction = Direction.UP, + input: MutableDeviceState = MutableDeviceState(0), + hold: MutableDeviceState = MutableDeviceState(false) +) : Transmission>(context) { + + override val input: MutableDeviceState by property(MetaConverter.int, input) + + public val hold: MutableDeviceState by property(MetaConverter.boolean, hold) + + override val output: DeviceState> = combineState( + input, hold + ) { input, hold -> + //TODO use hold parameter + zero + input * direction.coef * step + } +} \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Transmission.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Transmission.kt new file mode 100644 index 0000000..5974a2f --- /dev/null +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/Transmission.kt @@ -0,0 +1,33 @@ +package space.kscience.controls.constructor.library + +import space.kscience.controls.constructor.DeviceConstructor +import space.kscience.controls.constructor.DeviceState +import space.kscience.controls.constructor.MutableDeviceState +import space.kscience.controls.constructor.flowState +import space.kscience.dataforge.context.Context + + +/** + * A model for a device that converts one type of physical quantity to another type + */ +public abstract class Transmission(context: Context) : DeviceConstructor(context) { + public abstract val input: MutableDeviceState + public abstract val output: DeviceState + + public companion object { + /** + * Create a device that is a hard connection between two physical quantities + */ + public suspend fun direct( + context: Context, + input: MutableDeviceState, + transform: suspend (T) -> R + ): Transmission { + val initialValue = transform(input.value) + return object : Transmission(context) { + override val input: MutableDeviceState = input + override val output: DeviceState = flowState(input, initialValue) { emit(transform(it)) } + } + } + } +} \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/Direction.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/Direction.kt new file mode 100644 index 0000000..3685c7e --- /dev/null +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/Direction.kt @@ -0,0 +1,6 @@ +package space.kscience.controls.constructor.units + +public enum class Direction(public val coef: Int) { + UP(1), + DOWN(-1) +} \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/NumericalValue.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/NumericalValue.kt index c97784d..98fe418 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/NumericalValue.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/NumericalValue.kt @@ -7,4 +7,31 @@ import kotlin.jvm.JvmInline * A value without identity coupled to units of measurements. */ @JvmInline -public value class NumericalValue(public val value: Double) \ No newline at end of file +public value class NumericalValue(public val value: Double) + +public operator fun NumericalValue.compareTo(other: NumericalValue): Int = + value.compareTo(other.value) + +public operator fun NumericalValue.plus( + other: NumericalValue +): NumericalValue = NumericalValue(this.value + other.value) + +public operator fun NumericalValue.minus( + other: NumericalValue +): NumericalValue = NumericalValue(this.value - other.value) + +public operator fun NumericalValue.times( + c: Number +): NumericalValue = NumericalValue(this.value * c.toDouble()) + +public operator fun Number.times( + numericalValue: NumericalValue +): NumericalValue = NumericalValue(numericalValue.value * toDouble()) + +public operator fun NumericalValue.times( + c: Double +): NumericalValue = NumericalValue(this.value * c) + +public operator fun NumericalValue.div( + c: Number +): NumericalValue = NumericalValue(this.value / c.toDouble()) \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/UnitsOfMeasurement.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/UnitsOfMeasurement.kt index c29c1be..ed295ef 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/UnitsOfMeasurement.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/UnitsOfMeasurement.kt @@ -7,24 +7,31 @@ public interface UnitsOfMeasurement public interface UnitsOfLength : UnitsOfMeasurement -public data object Meters: UnitsOfLength +public data object Meters : UnitsOfLength /**/ -public interface UnitsOfTime: UnitsOfMeasurement +public interface UnitsOfTime : UnitsOfMeasurement -public data object Seconds: UnitsOfTime +public data object Seconds : UnitsOfTime /**/ -public interface UnitsOfVelocity: UnitsOfMeasurement +public interface UnitsOfVelocity : UnitsOfMeasurement -public data object MetersPerSecond: UnitsOfVelocity +public data object MetersPerSecond : UnitsOfVelocity /**/ -public interface UnitsAngularOfVelocity: UnitsOfMeasurement +public sealed interface UnitsOfAngles : UnitsOfMeasurement -public data object RadiansPerSecond: UnitsAngularOfVelocity +public data object Radians : UnitsOfAngles +public data object Degrees : UnitsOfAngles -public data object DegreesPerSecond: UnitsAngularOfVelocity \ No newline at end of file +/**/ + +public interface UnitsAngularOfVelocity : UnitsOfMeasurement + +public data object RadiansPerSecond : UnitsAngularOfVelocity + +public data object DegreesPerSecond : UnitsAngularOfVelocity \ No newline at end of file diff --git a/controls-constructor/src/commonTest/kotlin/space/kscience/controls/constructor/TimerTest.kt b/controls-constructor/src/commonTest/kotlin/space/kscience/controls/constructor/TimerTest.kt index 323f8df..38f7d83 100644 --- a/controls-constructor/src/commonTest/kotlin/space/kscience/controls/constructor/TimerTest.kt +++ b/controls-constructor/src/commonTest/kotlin/space/kscience/controls/constructor/TimerTest.kt @@ -15,8 +15,9 @@ class TimerTest { @Test fun timer() = runTest { val timer = TimerState(Global.request(ClockManager), 10.milliseconds) - timer.valueFlow.take(10).onEach { + timer.valueFlow.take(100).onEach { println(it) }.collect() + } } \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/ClockManager.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/ClockManager.kt index a245d29..4204730 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/ClockManager.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/ClockManager.kt @@ -9,6 +9,7 @@ import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.double import kotlin.coroutines.CoroutineContext import kotlin.math.roundToLong +import kotlin.time.Duration @OptIn(InternalCoroutinesApi::class) private class CompressedTimeDispatcher( @@ -78,6 +79,14 @@ public class ClockManager : AbstractPlugin() { CompressedTimeDispatcher(this, dispatcher, timeCompression) } + public fun scheduleWithFixedDelay(tick: Duration, block: suspend () -> Unit): Job = context.launch(asDispatcher()) { + while (isActive) { + delay(tick) + block() + } + } + + public companion object : PluginFactory { override val tag: PluginTag = PluginTag("clock", group = PluginTag.DATAFORGE_GROUP) diff --git a/demo/constructor/src/jvmMain/kotlin/BodyOnSprings.kt b/demo/constructor/src/jvmMain/kotlin/BodyOnSprings.kt index 9f84cb7..7fcb5ab 100644 --- a/demo/constructor/src/jvmMain/kotlin/BodyOnSprings.kt +++ b/demo/constructor/src/jvmMain/kotlin/BodyOnSprings.kt @@ -41,7 +41,7 @@ class Spring( val l0: Double, val begin: DeviceState, val end: DeviceState, -) : ConstructorModel(context) { +) : ModelConstructor(context) { /** * vector from start to end @@ -76,7 +76,7 @@ class MaterialPoint( val force: DeviceState, val position: MutableDeviceState, val velocity: MutableDeviceState = MutableDeviceState(XY.ZERO), -) : ConstructorModel(context, force, position, velocity) { +) : ModelConstructor(context, force, position, velocity) { private val timer: TimerState = timer(2.milliseconds) diff --git a/demo/constructor/src/jvmMain/kotlin/LinearDrive.kt b/demo/constructor/src/jvmMain/kotlin/PidDemo.kt similarity index 92% rename from demo/constructor/src/jvmMain/kotlin/LinearDrive.kt rename to demo/constructor/src/jvmMain/kotlin/PidDemo.kt index f64ef72..fb0ab64 100644 --- a/demo/constructor/src/jvmMain/kotlin/LinearDrive.kt +++ b/demo/constructor/src/jvmMain/kotlin/PidDemo.kt @@ -52,14 +52,18 @@ class LinearDrive( ) : DeviceConstructor(drive.context, meta) { val drive by device(drive) - val pid by device(PidRegulator(drive, pidParameters)) + val pid by device( + PidRegulator( + context = context, + position = drive.propertyAsState(Drive.position, 0.0), + pidParameters = pidParameters + ) + ) + + private val binding = bind(pid.output, drive.stateOfForce()) val start by device(start) val end by device(end) - - val position = drive.propertyAsState(Drive.position, Double.NaN) - - val target = pid.propertyAsState(Regulator.target, 0.0) } /** @@ -67,14 +71,14 @@ class LinearDrive( */ fun LinearDrive( context: Context, - positionState: DoubleInRangeState, + positionState: MutableDoubleInRangeState, mass: Double, pidParameters: PidParameters, meta: Meta = Meta.EMPTY, ): LinearDrive = LinearDrive( drive = VirtualDrive(context, mass, positionState), - start = VirtualLimitSwitch(context, positionState.atStart), - end = VirtualLimitSwitch(context, positionState.atEnd), + start = LimitSwitch(context, positionState.atStart), + end = LimitSwitch(context, positionState.atEnd), pidParameters = pidParameters, meta = meta ) @@ -116,7 +120,7 @@ fun main() = application { mutableStateOf(PidParameters(kp = 2.5, ki = 0.0, kd = -0.1, timeStep = 0.005.seconds)) } - val state = remember { DoubleInRangeState(0.0, -6.0..6.0) } + val state = remember { MutableDoubleInRangeState(0.0, -6.0..6.0) } val linearDrive = remember { context.install( @@ -128,7 +132,7 @@ fun main() = application { val modulator = remember { context.install( "modulator", - Modulator(context, linearDrive.target) + Modulator(context, linearDrive.pid.target) ) } @@ -207,7 +211,7 @@ fun main() = application { Slider( pidParameters.timeStep.toDouble(DurationUnit.MILLISECONDS).toFloat(), { pidParameters = pidParameters.copy(timeStep = it.toDouble().milliseconds) }, - valueRange = 0f..100f, + valueRange = 1f..100f, steps = 100 ) } @@ -248,14 +252,14 @@ fun main() = application { lineStyle = LineStyle(SolidColor(Color.Blue)) ) PlotDeviceProperty( - linearDrive.pid, - Regulator.position, + linearDrive.drive, + Drive.position, maxAge = maxAge, sampling = 50.milliseconds, ) - PlotDeviceProperty( - linearDrive.pid, - Regulator.target, + PlotNumberState( + context = context, + state = linearDrive.pid.target, maxAge = maxAge, sampling = 50.milliseconds, lineStyle = LineStyle(SolidColor(Color.Red))