WIP Constructor update
This commit is contained in:
parent
9edde7bdbd
commit
54e915ef10
@ -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 <T> StateContainer.stateOf(initialValue: T): MutableDeviceState<T> =
|
||||
MutableDeviceState(initialValue)
|
||||
)
|
||||
|
||||
public fun <T : ConstructorModel> StateContainer.model(model: T): T {
|
||||
public fun <T : ModelConstructor> StateContainer.model(model: T): T {
|
||||
registerElement(ModelConstructorElement(model))
|
||||
return model
|
||||
}
|
||||
@ -125,14 +125,13 @@ public fun <T1, T2, R> StateContainer.combineState(
|
||||
transformation: (T1, T2) -> R,
|
||||
): DeviceState<R> = 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 <T> StateContainer.bindTo(sourceState: DeviceState<T>, targetState: MutableDeviceState<T>): Job {
|
||||
public fun <T> StateContainer.bind(sourceState: DeviceState<T>, targetState: MutableDeviceState<T>): Job {
|
||||
val descriptor = ConnectionConstrucorElement(setOf(sourceState), setOf(targetState))
|
||||
registerElement(descriptor)
|
||||
return sourceState.valueFlow.onEach {
|
||||
|
@ -32,13 +32,14 @@ public abstract class DeviceConstructor(
|
||||
_constructorElements.remove(constructorElement)
|
||||
}
|
||||
|
||||
override fun <T> registerProperty(
|
||||
override fun <T, S: DeviceState<T>> registerAsProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptor: PropertyDescriptor,
|
||||
state: DeviceState<T>,
|
||||
) {
|
||||
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 <T, S : DeviceState<T>> 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 <T> DeviceConstructor.virtualProperty(
|
||||
nameOverride,
|
||||
)
|
||||
|
||||
public fun <T, S : DeviceState<T>> DeviceConstructor.property(
|
||||
public fun <T, S : DeviceState<T>> DeviceConstructor.registerAsProperty(
|
||||
spec: DevicePropertySpec<*, T>,
|
||||
state: S,
|
||||
): Unit = registerProperty(spec.converter, spec.descriptor, state)
|
||||
): S {
|
||||
registerAsProperty(spec.converter, spec.descriptor, state)
|
||||
return state
|
||||
}
|
||||
|
@ -93,11 +93,11 @@ public open class DeviceGroup(
|
||||
/**
|
||||
* Register a new property based on [DeviceState]. Properties could be modified dynamically
|
||||
*/
|
||||
public open fun <T> registerProperty(
|
||||
public open fun <T, S : DeviceState<T>> registerAsProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptor: PropertyDescriptor,
|
||||
state: DeviceState<T>,
|
||||
) {
|
||||
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<Name, Action> = hashMapOf()
|
||||
@ -174,8 +175,8 @@ public open class DeviceGroup(
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T> DeviceGroup.registerProperty(propertySpec: DevicePropertySpec<*, T>, state: DeviceState<T>) {
|
||||
registerProperty(propertySpec.converter, propertySpec.descriptor, state)
|
||||
public fun <T> DeviceGroup.registerAsProperty(propertySpec: DevicePropertySpec<*, T>, state: DeviceState<T>) {
|
||||
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 <T : Any> DeviceGroup.registerProperty(
|
||||
public fun <T : Any> DeviceGroup.registerAsProperty(
|
||||
name: String,
|
||||
converter: MetaConverter<T>,
|
||||
state: DeviceState<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
) {
|
||||
registerProperty(
|
||||
registerAsProperty(
|
||||
converter,
|
||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||
state
|
||||
@ -268,7 +269,7 @@ public fun <T : Any> DeviceGroup.registerMutableProperty(
|
||||
state: MutableDeviceState<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
) {
|
||||
registerProperty(
|
||||
registerAsProperty(
|
||||
converter,
|
||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||
state
|
||||
|
@ -67,9 +67,11 @@ public fun <T, R> DeviceState.Companion.map(
|
||||
|
||||
override val valueFlow: Flow<R> = state.valueFlow.map(mapper)
|
||||
|
||||
override fun toString(): String = "DeviceState.map(arg=${state})"
|
||||
override fun toString(): String = "DeviceState.map(state=${state})"
|
||||
}
|
||||
|
||||
public fun <T, R> DeviceState<T>.map(mapper: (T) -> R): DeviceStateWithDependencies<R> = DeviceState.map(this, mapper)
|
||||
|
||||
/**
|
||||
* Combine two device states into one read-only [DeviceState]. Only the latest value of each state is used.
|
||||
*/
|
||||
|
@ -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 {
|
@ -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<Double>,
|
||||
) : MutableDeviceState<Double> {
|
||||
|
||||
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<Double> get() = _valueFlow
|
||||
|
||||
/**
|
||||
* A state showing that the range is on its lower boundary
|
||||
*/
|
||||
public val atStart: DeviceState<Boolean> = DeviceState.map(this) {
|
||||
it <= range.start
|
||||
}
|
||||
|
||||
/**
|
||||
* A state showing that the range is on its higher boundary
|
||||
*/
|
||||
public val atEnd: DeviceState<Boolean> = 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<Double>,
|
||||
): DoubleInRangeState = DoubleInRangeState(initialValue, range).also {
|
||||
registerElement(StateConstructorElement(it))
|
||||
}
|
@ -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 <T> MutableDeviceState(
|
||||
initialValue: T,
|
||||
callback: (T) -> Unit = {},
|
||||
): MutableDeviceState<T> = VirtualDeviceState(initialValue, callback)
|
||||
|
||||
|
||||
/**
|
||||
* Create a [DeviceState] with constant value
|
||||
*/
|
||||
public fun <T> DeviceState(
|
||||
value: T
|
||||
): DeviceState<T> = object : DeviceState<T> {
|
||||
override val value: T get() = value
|
||||
override val valueFlow: Flow<T>
|
||||
get() = emptyFlow()
|
||||
|
||||
override fun toString(): String = "ConstDeviceState($value)"
|
||||
|
||||
}
|
@ -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<T, R>(
|
||||
context: Context,
|
||||
input: DeviceState<T>,
|
||||
initialValue: R,
|
||||
transform: suspend FlowCollector<R>.(T) -> Unit,
|
||||
) : DeviceConstructor(context) {
|
||||
public val output: DeviceStateWithDependencies<R> = flowState(input, initialValue, transform)
|
||||
}
|
@ -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<Double>,
|
||||
public val range: ClosedFloatingPointRange<Double>,
|
||||
) : DeviceState<Double> {
|
||||
|
||||
override val valueFlow: Flow<Double> 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<Boolean> = input.map { it <= range.start }
|
||||
|
||||
/**
|
||||
* A state showing that the range is on its higher boundary
|
||||
*/
|
||||
public val atEnd: DeviceState<Boolean> = input.map { it >= range.endInclusive }
|
||||
|
||||
override fun toString(): String = "DoubleRangeState(value=${value},range=$range)"
|
||||
}
|
||||
|
||||
public class MutableDoubleInRangeState(
|
||||
private val mutableInput: MutableDeviceState<Double>,
|
||||
range: ClosedFloatingPointRange<Double>
|
||||
) : DoubleInRangeState(mutableInput, range), MutableDeviceState<Double> {
|
||||
override var value: Double
|
||||
get() = super.value
|
||||
set(value) {
|
||||
mutableInput.value = value.coerceIn(range)
|
||||
}
|
||||
}
|
||||
|
||||
public fun MutableDoubleInRangeState(
|
||||
initialValue: Double,
|
||||
range: ClosedFloatingPointRange<Double>
|
||||
): MutableDoubleInRangeState = MutableDoubleInRangeState(MutableDeviceState(initialValue),range)
|
||||
|
||||
|
||||
public fun DeviceState<Double>.coerceIn(
|
||||
range: ClosedFloatingPointRange<Double>
|
||||
): DoubleInRangeState = DoubleInRangeState(this, range)
|
||||
|
||||
public fun MutableDeviceState<Double>.coerceIn(
|
||||
range: ClosedFloatingPointRange<Double>
|
||||
): MutableDoubleInRangeState = MutableDoubleInRangeState(this, range)
|
@ -98,4 +98,5 @@ public class VirtualDrive(
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun Drive.stateOfForce(): MutableDeviceState<Double> = propertyAsState(Drive.force)
|
||||
public fun Drive.stateOfForce(initialForce: Double = 0.0): MutableDeviceState<Double> =
|
||||
propertyAsState(Drive.force, initialForce)
|
||||
|
@ -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<LimitSwitch>() {
|
||||
public val locked: DevicePropertySpec<LimitSwitch, Boolean> by booleanProperty { isLocked() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual [LimitSwitch]
|
||||
*/
|
||||
public class VirtualLimitSwitch(
|
||||
public class LimitSwitch(
|
||||
context: Context,
|
||||
locked: DeviceState<Boolean>,
|
||||
) : DeviceConstructor(context), LimitSwitch {
|
||||
) : DeviceConstructor(context) {
|
||||
|
||||
public val locked: DeviceState<Boolean> by property(MetaConverter.boolean, locked)
|
||||
public val locked: DeviceState<Boolean> = registerAsProperty(LimitSwitch.locked, locked)
|
||||
|
||||
override fun isLocked(): Boolean = locked.value
|
||||
public companion object : DeviceSpec<LimitSwitch>() {
|
||||
public val locked: DevicePropertySpec<LimitSwitch, Boolean> by booleanProperty { locked.value }
|
||||
}
|
||||
}
|
||||
|
||||
public fun <U : UnitsOfMeasurement, T : NumericalValue<U>> LimitSwitch(
|
||||
context: Context,
|
||||
limit: T,
|
||||
boundary: Direction,
|
||||
position: DeviceState<T>,
|
||||
): LimitSwitch = LimitSwitch(
|
||||
context,
|
||||
DeviceState.map(position) {
|
||||
when (boundary) {
|
||||
Direction.UP -> it >= limit
|
||||
Direction.DOWN -> it <= limit
|
||||
}
|
||||
}
|
||||
)
|
@ -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<Double>,
|
||||
public var pidParameters: PidParameters, // TODO expose as property
|
||||
) : DeviceBySpec<Regulator>(Regulator, drive.context), Regulator {
|
||||
output: MutableDeviceState<Double> = MutableDeviceState(0.0),
|
||||
) : Regulator<Double>(context) {
|
||||
|
||||
private val clock = drive.context.clock
|
||||
override val target: MutableDeviceState<Double> = stateOf(0.0)
|
||||
override val output: MutableDeviceState<Double> = 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 {
|
||||
private val updateJob = launch {
|
||||
while (isActive) {
|
||||
delay(pidParameters.timeStep)
|
||||
mutex.withLock {
|
||||
val realTime = clock.now()
|
||||
val delta = target - getPosition()
|
||||
val delta = target.value - position.value
|
||||
val dtSeconds = (realTime - lastTime).toDouble(DurationUnit.SECONDS)
|
||||
integral += delta * dtSeconds
|
||||
val derivative = (drive.position - lastPosition) / dtSeconds
|
||||
val derivative = (position.value - lastPosition) / dtSeconds
|
||||
|
||||
//set last time and value to new values
|
||||
lastTime = realTime
|
||||
lastPosition = drive.position
|
||||
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))
|
||||
//
|
||||
//public fun DeviceGroup.pid(
|
||||
// name: String,
|
||||
// drive: Drive,
|
||||
// pidParameters: PidParameters,
|
||||
//): PidRegulator = install(name.parseAsName(), PidRegulator(drive, pidParameters))
|
@ -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<T>(context: Context) : DeviceConstructor(context) {
|
||||
|
||||
/**
|
||||
* Current position value
|
||||
*/
|
||||
public suspend fun getPosition(): Double
|
||||
public abstract val target: MutableDeviceState<T>
|
||||
|
||||
public companion object : DeviceSpec<Regulator>() {
|
||||
public val target: MutableDevicePropertySpec<Regulator, Double> by mutableProperty(MetaConverter.double, Regulator::target)
|
||||
|
||||
public val position: DevicePropertySpec<Regulator, Double> by doubleProperty { getPosition() }
|
||||
}
|
||||
public abstract val output: DeviceState<T>
|
||||
}
|
@ -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<Degrees>,
|
||||
public val zero: NumericalValue<Degrees> = NumericalValue(0.0),
|
||||
direction: Direction = Direction.UP,
|
||||
input: MutableDeviceState<Int> = MutableDeviceState(0),
|
||||
hold: MutableDeviceState<Boolean> = MutableDeviceState(false)
|
||||
) : Transmission<Int, NumericalValue<Degrees>>(context) {
|
||||
|
||||
override val input: MutableDeviceState<Int> by property(MetaConverter.int, input)
|
||||
|
||||
public val hold: MutableDeviceState<Boolean> by property(MetaConverter.boolean, hold)
|
||||
|
||||
override val output: DeviceState<NumericalValue<Degrees>> = combineState(
|
||||
input, hold
|
||||
) { input, hold ->
|
||||
//TODO use hold parameter
|
||||
zero + input * direction.coef * step
|
||||
}
|
||||
}
|
@ -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<T, R>(context: Context) : DeviceConstructor(context) {
|
||||
public abstract val input: MutableDeviceState<T>
|
||||
public abstract val output: DeviceState<R>
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* Create a device that is a hard connection between two physical quantities
|
||||
*/
|
||||
public suspend fun <T, R> direct(
|
||||
context: Context,
|
||||
input: MutableDeviceState<T>,
|
||||
transform: suspend (T) -> R
|
||||
): Transmission<T, R> {
|
||||
val initialValue = transform(input.value)
|
||||
return object : Transmission<T, R>(context) {
|
||||
override val input: MutableDeviceState<T> = input
|
||||
override val output: DeviceState<R> = flowState(input, initialValue) { emit(transform(it)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package space.kscience.controls.constructor.units
|
||||
|
||||
public enum class Direction(public val coef: Int) {
|
||||
UP(1),
|
||||
DOWN(-1)
|
||||
}
|
@ -7,4 +7,31 @@ import kotlin.jvm.JvmInline
|
||||
* A value without identity coupled to units of measurements.
|
||||
*/
|
||||
@JvmInline
|
||||
public value class NumericalValue<T: UnitsOfMeasurement>(public val value: Double)
|
||||
public value class NumericalValue<U : UnitsOfMeasurement>(public val value: Double)
|
||||
|
||||
public operator fun <U: UnitsOfMeasurement> NumericalValue<U>.compareTo(other: NumericalValue<U>): Int =
|
||||
value.compareTo(other.value)
|
||||
|
||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.plus(
|
||||
other: NumericalValue<U>
|
||||
): NumericalValue<U> = NumericalValue(this.value + other.value)
|
||||
|
||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.minus(
|
||||
other: NumericalValue<U>
|
||||
): NumericalValue<U> = NumericalValue(this.value - other.value)
|
||||
|
||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.times(
|
||||
c: Number
|
||||
): NumericalValue<U> = NumericalValue(this.value * c.toDouble())
|
||||
|
||||
public operator fun <U : UnitsOfMeasurement> Number.times(
|
||||
numericalValue: NumericalValue<U>
|
||||
): NumericalValue<U> = NumericalValue(numericalValue.value * toDouble())
|
||||
|
||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.times(
|
||||
c: Double
|
||||
): NumericalValue<U> = NumericalValue(this.value * c)
|
||||
|
||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.div(
|
||||
c: Number
|
||||
): NumericalValue<U> = NumericalValue(this.value / c.toDouble())
|
@ -23,6 +23,13 @@ public data object MetersPerSecond: UnitsOfVelocity
|
||||
|
||||
/**/
|
||||
|
||||
public sealed interface UnitsOfAngles : UnitsOfMeasurement
|
||||
|
||||
public data object Radians : UnitsOfAngles
|
||||
public data object Degrees : UnitsOfAngles
|
||||
|
||||
/**/
|
||||
|
||||
public interface UnitsAngularOfVelocity : UnitsOfMeasurement
|
||||
|
||||
public data object RadiansPerSecond : UnitsAngularOfVelocity
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
}
|
@ -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<ClockManager> {
|
||||
override val tag: PluginTag = PluginTag("clock", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
|
@ -41,7 +41,7 @@ class Spring(
|
||||
val l0: Double,
|
||||
val begin: DeviceState<XY>,
|
||||
val end: DeviceState<XY>,
|
||||
) : ConstructorModel(context) {
|
||||
) : ModelConstructor(context) {
|
||||
|
||||
/**
|
||||
* vector from start to end
|
||||
@ -76,7 +76,7 @@ class MaterialPoint(
|
||||
val force: DeviceState<XY>,
|
||||
val position: MutableDeviceState<XY>,
|
||||
val velocity: MutableDeviceState<XY> = MutableDeviceState(XY.ZERO),
|
||||
) : ConstructorModel(context, force, position, velocity) {
|
||||
) : ModelConstructor(context, force, position, velocity) {
|
||||
|
||||
private val timer: TimerState = timer(2.milliseconds)
|
||||
|
||||
|
@ -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))
|
Loading…
Reference in New Issue
Block a user