Fix step drive demo
This commit is contained in:
parent
d0e3faea88
commit
a2b7d1ecb0
@ -32,12 +32,12 @@ public abstract class DeviceConstructor(
|
|||||||
_constructorElements.remove(constructorElement)
|
_constructorElements.remove(constructorElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T, S: DeviceState<T>> registerAsProperty(
|
override fun <T, S: DeviceState<T>> registerProperty(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
descriptor: PropertyDescriptor,
|
descriptor: PropertyDescriptor,
|
||||||
state: S,
|
state: S,
|
||||||
): S {
|
): S {
|
||||||
val res = super.registerAsProperty(converter, descriptor, state)
|
val res = super.registerProperty(converter, descriptor, state)
|
||||||
registerElement(PropertyConstructorElement(this, descriptor.name, state))
|
registerElement(PropertyConstructorElement(this, descriptor.name, state))
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ public fun <T, S : DeviceState<T>> DeviceConstructor.property(
|
|||||||
PropertyDelegateProvider { _: DeviceConstructor, property ->
|
PropertyDelegateProvider { _: DeviceConstructor, property ->
|
||||||
val name = nameOverride ?: property.name
|
val name = nameOverride ?: property.name
|
||||||
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
|
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
|
||||||
registerAsProperty(converter, descriptor, state)
|
registerProperty(converter, descriptor, state)
|
||||||
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
@ -145,6 +145,6 @@ public fun <T, S : DeviceState<T>> DeviceConstructor.registerAsProperty(
|
|||||||
spec: DevicePropertySpec<*, T>,
|
spec: DevicePropertySpec<*, T>,
|
||||||
state: S,
|
state: S,
|
||||||
): S {
|
): S {
|
||||||
registerAsProperty(spec.converter, spec.descriptor, state)
|
registerProperty(spec.converter, spec.descriptor, state)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
@ -42,10 +42,15 @@ public open class DeviceGroup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Action(
|
private class Action<T, R>(
|
||||||
val invoke: suspend (Meta?) -> Meta?,
|
val inputConverter: MetaConverter<T>,
|
||||||
|
val outputConverter: MetaConverter<R>,
|
||||||
val descriptor: ActionDescriptor,
|
val descriptor: ActionDescriptor,
|
||||||
)
|
val action: suspend (T) -> R,
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(argument: Meta?): Meta? = argument?.let { inputConverter.readOrNull(it) }
|
||||||
|
?.let { action(it)?.let { outputConverter.convert(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private val sharedMessageFlow = MutableSharedFlow<DeviceMessage>()
|
private val sharedMessageFlow = MutableSharedFlow<DeviceMessage>()
|
||||||
@ -93,7 +98,7 @@ public open class DeviceGroup(
|
|||||||
/**
|
/**
|
||||||
* Register a new property based on [DeviceState]. Properties could be modified dynamically
|
* Register a new property based on [DeviceState]. Properties could be modified dynamically
|
||||||
*/
|
*/
|
||||||
public open fun <T, S : DeviceState<T>> registerAsProperty(
|
public open fun <T, S : DeviceState<T>> registerProperty(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
descriptor: PropertyDescriptor,
|
descriptor: PropertyDescriptor,
|
||||||
state: S,
|
state: S,
|
||||||
@ -112,7 +117,26 @@ public open class DeviceGroup(
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
private val actions: MutableMap<Name, Action> = hashMapOf()
|
private val actions: MutableMap<Name, Action<*, *>> = hashMapOf()
|
||||||
|
|
||||||
|
public fun <T, R> registerAction(
|
||||||
|
inputConverter: MetaConverter<T>,
|
||||||
|
outputConverter: MetaConverter<R>,
|
||||||
|
descriptor: ActionDescriptor,
|
||||||
|
action: suspend (T) -> R,
|
||||||
|
): suspend (T) -> R {
|
||||||
|
val name = descriptor.name.parseAsName()
|
||||||
|
require(actions[name] == null) { "Can't add action with name $name. It already exists." }
|
||||||
|
actions[name] = Action(
|
||||||
|
inputConverter = inputConverter,
|
||||||
|
outputConverter = outputConverter,
|
||||||
|
descriptor = descriptor,
|
||||||
|
action = action
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
action(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
override val propertyDescriptors: Collection<PropertyDescriptor>
|
||||||
get() = properties.values.map { it.descriptor }
|
get() = properties.values.map { it.descriptor }
|
||||||
@ -137,8 +161,8 @@ public open class DeviceGroup(
|
|||||||
|
|
||||||
|
|
||||||
override suspend fun execute(actionName: String, argument: Meta?): Meta? {
|
override suspend fun execute(actionName: String, argument: Meta?): Meta? {
|
||||||
val action = actions[actionName] ?: error("Action with name $actionName not found")
|
val action: Action<*, *> = actions[actionName] ?: error("Action with name $actionName not found")
|
||||||
return action.invoke(argument)
|
return action(argument)
|
||||||
}
|
}
|
||||||
|
|
||||||
final override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.STOPPED
|
final override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.STOPPED
|
||||||
@ -176,7 +200,7 @@ public open class DeviceGroup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun <T> DeviceGroup.registerAsProperty(propertySpec: DevicePropertySpec<*, T>, state: DeviceState<T>) {
|
public fun <T> DeviceGroup.registerAsProperty(propertySpec: DevicePropertySpec<*, T>, state: DeviceState<T>) {
|
||||||
registerAsProperty(propertySpec.converter, propertySpec.descriptor, state)
|
registerProperty(propertySpec.converter, propertySpec.descriptor, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun DeviceManager.registerDeviceGroup(
|
public fun DeviceManager.registerDeviceGroup(
|
||||||
@ -253,7 +277,7 @@ public fun <T : Any> DeviceGroup.registerAsProperty(
|
|||||||
state: DeviceState<T>,
|
state: DeviceState<T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
registerAsProperty(
|
registerProperty(
|
||||||
converter,
|
converter,
|
||||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||||
state
|
state
|
||||||
@ -269,7 +293,7 @@ public fun <T : Any> DeviceGroup.registerMutableProperty(
|
|||||||
state: MutableDeviceState<T>,
|
state: MutableDeviceState<T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
registerAsProperty(
|
registerProperty(
|
||||||
converter,
|
converter,
|
||||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
PropertyDescriptor(name).apply(descriptorBuilder),
|
||||||
state
|
state
|
||||||
|
@ -74,7 +74,7 @@ public fun <T, R> DeviceState.Companion.map(
|
|||||||
|
|
||||||
public fun <T, R> DeviceState<T>.map(mapper: (T) -> R): DeviceStateWithDependencies<R> = DeviceState.map(this, mapper)
|
public fun <T, R> DeviceState<T>.map(mapper: (T) -> R): DeviceStateWithDependencies<R> = DeviceState.map(this, mapper)
|
||||||
|
|
||||||
public fun DeviceState<out NumericalValue<out UnitsOfMeasurement>>.values(): DeviceState<Double> = object : DeviceState<Double> {
|
public fun DeviceState<NumericalValue<out UnitsOfMeasurement>>.values(): DeviceState<Double> = object : DeviceState<Double> {
|
||||||
override val value: Double
|
override val value: Double
|
||||||
get() = this@values.value.value
|
get() = this@values.value.value
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package space.kscience.controls.constructor.devices
|
package space.kscience.controls.constructor.devices
|
||||||
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import space.kscience.controls.constructor.*
|
import space.kscience.controls.constructor.*
|
||||||
import space.kscience.controls.constructor.units.Degrees
|
import space.kscience.controls.constructor.units.Degrees
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
import space.kscience.controls.constructor.units.NumericalValue
|
||||||
@ -16,38 +15,36 @@ import kotlin.time.DurationUnit
|
|||||||
/**
|
/**
|
||||||
* A step drive
|
* A step drive
|
||||||
*
|
*
|
||||||
* @param speed ticks per second
|
* @param ticksPerSecond ticks per second
|
||||||
* @param target target ticks state
|
* @param target target ticks state
|
||||||
* @param writeTicks a hardware callback
|
* @param writeTicks a hardware callback
|
||||||
*/
|
*/
|
||||||
public class StepDrive(
|
public class StepDrive(
|
||||||
context: Context,
|
context: Context,
|
||||||
speed: MutableDeviceState<Double>,
|
ticksPerSecond: MutableDeviceState<Double>,
|
||||||
target: MutableDeviceState<Int> = MutableDeviceState(0),
|
target: MutableDeviceState<Int> = MutableDeviceState(0),
|
||||||
private val writeTicks: suspend (ticks: Int, speed: Double) -> Unit,
|
private val writeTicks: suspend (ticks: Int, speed: Double) -> Unit = { _, _ -> },
|
||||||
) : DeviceConstructor(context) {
|
) : DeviceConstructor(context) {
|
||||||
|
|
||||||
public val target: MutableDeviceState<Int> by property(MetaConverter.int, target)
|
public val target: MutableDeviceState<Int> by property(MetaConverter.int, target)
|
||||||
|
|
||||||
public val speed: MutableDeviceState<Double> by property(MetaConverter.double, speed)
|
public val speed: MutableDeviceState<Double> by property(MetaConverter.double, ticksPerSecond)
|
||||||
|
|
||||||
private val positionState = stateOf(target.value)
|
private val positionState = stateOf(target.value)
|
||||||
|
|
||||||
public val position: DeviceState<Int> by property(MetaConverter.int, positionState)
|
public val position: DeviceState<Int> by property(MetaConverter.int, positionState)
|
||||||
|
|
||||||
private val ticker = onTimer { prev, next ->
|
private val ticker = onTimer(reads = setOf(target, position), writes = setOf(position)) { prev, next ->
|
||||||
val tickSpeed = speed.value
|
val tickSpeed = ticksPerSecond.value
|
||||||
val timeDelta = (next - prev).toDouble(DurationUnit.SECONDS)
|
val timeDelta = (next - prev).toDouble(DurationUnit.SECONDS)
|
||||||
val ticksDelta: Int = target.value - position.value
|
val ticksDelta: Int = target.value - position.value
|
||||||
val steps: Int = when {
|
val steps: Int = when {
|
||||||
ticksDelta > 0 -> min(ticksDelta, (timeDelta * tickSpeed).roundToInt())
|
ticksDelta > 0 -> min(ticksDelta, (timeDelta * tickSpeed).roundToInt())
|
||||||
ticksDelta < 0 -> max(ticksDelta, (timeDelta * tickSpeed).roundToInt())
|
ticksDelta < 0 -> max(ticksDelta, -(timeDelta * tickSpeed).roundToInt())
|
||||||
else -> return@onTimer
|
else -> return@onTimer
|
||||||
}
|
}
|
||||||
launch {
|
writeTicks(steps, tickSpeed)
|
||||||
writeTicks(steps, tickSpeed)
|
positionState.value += steps
|
||||||
positionState.value += steps
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +52,9 @@ public class StepDrive(
|
|||||||
* Compute a state using given tick-to-angle transformation
|
* Compute a state using given tick-to-angle transformation
|
||||||
*/
|
*/
|
||||||
public fun StepDrive.angle(
|
public fun StepDrive.angle(
|
||||||
zero: NumericalValue<Degrees>,
|
|
||||||
step: NumericalValue<Degrees>,
|
step: NumericalValue<Degrees>,
|
||||||
): DeviceState<NumericalValue<Degrees>> = position.map { zero + it * step }
|
zero: NumericalValue<Degrees> = NumericalValue(0),
|
||||||
|
): DeviceState<NumericalValue<Degrees>> = position.map {
|
||||||
|
zero + it.toDouble() * step
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ private class StateFlowAsState<T>(
|
|||||||
override var value: T by flow::value
|
override var value: T by flow::value
|
||||||
override val valueFlow: Flow<T> get() = flow
|
override val valueFlow: Flow<T> get() = flow
|
||||||
|
|
||||||
override fun toString(): String = "FlowAsState()"
|
override fun toString(): String = "FlowAsState($value)"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +23,7 @@ private class VirtualDeviceState<T>(
|
|||||||
callback(value)
|
callback(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "VirtualDeviceState()"
|
override fun toString(): String = "VirtualDeviceState($value)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,9 @@ public open class RangeState<T : Comparable<T>>(
|
|||||||
public val range: ClosedRange<T>,
|
public val range: ClosedRange<T>,
|
||||||
) : DeviceState<T> {
|
) : DeviceState<T> {
|
||||||
|
|
||||||
override val valueFlow: Flow<T> get() = input.valueFlow.map { it.coerceIn(range) }
|
override val valueFlow: Flow<T> get() = input.valueFlow.map {
|
||||||
|
it.coerceIn(range)
|
||||||
|
}
|
||||||
|
|
||||||
override val value: T get() = input.value.coerceIn(range)
|
override val value: T get() = input.value.coerceIn(range)
|
||||||
|
|
||||||
@ -59,10 +61,10 @@ public fun <U : UnitsOfMeasurement> MutableRangeState(
|
|||||||
|
|
||||||
|
|
||||||
public fun <T : Comparable<T>> DeviceState<T>.coerceIn(
|
public fun <T : Comparable<T>> DeviceState<T>.coerceIn(
|
||||||
range: ClosedFloatingPointRange<T>,
|
range: ClosedRange<T>,
|
||||||
): RangeState<T> = RangeState(this, range)
|
): RangeState<T> = RangeState(this, range)
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Comparable<T>> MutableDeviceState<T>.coerceIn(
|
public fun <T : Comparable<T>> MutableDeviceState<T>.coerceIn(
|
||||||
range: ClosedFloatingPointRange<T>,
|
range: ClosedRange<T>,
|
||||||
): MutableRangeState<T> = MutableRangeState(this, range)
|
): MutableRangeState<T> = MutableRangeState(this, range)
|
||||||
|
@ -3,20 +3,26 @@ package space.kscience.controls.constructor.models
|
|||||||
import space.kscience.controls.constructor.DeviceState
|
import space.kscience.controls.constructor.DeviceState
|
||||||
import space.kscience.controls.constructor.ModelConstructor
|
import space.kscience.controls.constructor.ModelConstructor
|
||||||
import space.kscience.controls.constructor.map
|
import space.kscience.controls.constructor.map
|
||||||
import space.kscience.controls.constructor.units.Meters
|
import space.kscience.controls.constructor.units.*
|
||||||
import space.kscience.controls.constructor.units.Newtons
|
|
||||||
import space.kscience.controls.constructor.units.NewtonsMeters
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
|
import kotlin.math.PI
|
||||||
|
|
||||||
public class ScrewDrive(
|
public class ScrewDrive(
|
||||||
context: Context,
|
context: Context,
|
||||||
public val leverage: NumericalValue<Meters>,
|
public val leverage: NumericalValue<Meters>,
|
||||||
) : ModelConstructor(context) {
|
) : ModelConstructor(context) {
|
||||||
|
|
||||||
public fun transformForce(
|
public fun transformForce(
|
||||||
stateOfForce: DeviceState<NumericalValue<NewtonsMeters>>,
|
stateOfForce: DeviceState<NumericalValue<NewtonsMeters>>,
|
||||||
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfForce) {
|
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfForce) {
|
||||||
NumericalValue(it.value * leverage.value)
|
NumericalValue(it.value * leverage.value/2/ PI)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun transformOffset(
|
||||||
|
stateOfAngle: DeviceState<NumericalValue<Degrees>>,
|
||||||
|
offset: NumericalValue<Meters> = NumericalValue(0),
|
||||||
|
): DeviceState<NumericalValue<Meters>> = DeviceState.map(stateOfAngle) {
|
||||||
|
offset + NumericalValue(it.value * leverage.value/2/ PI)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package space.kscience.controls.constructor.models
|
||||||
|
|
||||||
|
import space.kscience.controls.constructor.ModelConstructor
|
||||||
|
import space.kscience.controls.constructor.MutableDeviceState
|
||||||
|
import space.kscience.controls.constructor.stateOf
|
||||||
|
import space.kscience.controls.constructor.units.Meters
|
||||||
|
import space.kscience.controls.constructor.units.NumericalValue
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
|
||||||
|
public class XYPosition(
|
||||||
|
context: Context,
|
||||||
|
initialX: NumericalValue<Meters> = NumericalValue(0.0),
|
||||||
|
initialY: NumericalValue<Meters> = NumericalValue(0.0),
|
||||||
|
) : ModelConstructor(context) {
|
||||||
|
public val x: MutableDeviceState<NumericalValue<Meters>> = stateOf(initialX)
|
||||||
|
public val y: MutableDeviceState<NumericalValue<Meters>> = stateOf(initialY)
|
||||||
|
}
|
@ -10,7 +10,7 @@ import kotlin.jvm.JvmInline
|
|||||||
* A value without identity coupled to units of measurements.
|
* A value without identity coupled to units of measurements.
|
||||||
*/
|
*/
|
||||||
@JvmInline
|
@JvmInline
|
||||||
public value class NumericalValue<U : UnitsOfMeasurement>(public val value: Double): Comparable<NumericalValue<U>> {
|
public value class NumericalValue<U : UnitsOfMeasurement>(public val value: Double) : Comparable<NumericalValue<U>> {
|
||||||
override fun compareTo(other: NumericalValue<U>): Int = value.compareTo(other.value)
|
override fun compareTo(other: NumericalValue<U>): Int = value.compareTo(other.value)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -43,6 +43,9 @@ public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.div(
|
|||||||
c: Number,
|
c: Number,
|
||||||
): NumericalValue<U> = NumericalValue(this.value / c.toDouble())
|
): NumericalValue<U> = NumericalValue(this.value / c.toDouble())
|
||||||
|
|
||||||
|
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.div(other: NumericalValue<U>): Double =
|
||||||
|
value / other.value
|
||||||
|
|
||||||
|
|
||||||
private object NumericalValueMetaConverter : MetaConverter<NumericalValue<*>> {
|
private object NumericalValueMetaConverter : MetaConverter<NumericalValue<*>> {
|
||||||
override fun convert(obj: NumericalValue<*>): Meta = Meta(obj.value)
|
override fun convert(obj: NumericalValue<*>): Meta = Meta(obj.value)
|
||||||
|
@ -23,19 +23,19 @@ import kotlin.time.Duration.Companion.milliseconds
|
|||||||
import kotlin.time.DurationUnit
|
import kotlin.time.DurationUnit
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class XY(val x: Double, val y: Double) {
|
private data class XY(val x: Double, val y: Double) {
|
||||||
companion object {
|
companion object {
|
||||||
val ZERO = XY(0.0, 0.0)
|
val ZERO = XY(0.0, 0.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val XY.length: Double get() = sqrt(x.pow(2) + y.pow(2))
|
private val XY.length: Double get() = sqrt(x.pow(2) + y.pow(2))
|
||||||
|
|
||||||
operator fun XY.plus(other: XY): XY = XY(x + other.x, y + other.y)
|
private operator fun XY.plus(other: XY): XY = XY(x + other.x, y + other.y)
|
||||||
operator fun XY.times(c: Double): XY = XY(x * c, y * c)
|
private operator fun XY.times(c: Double): XY = XY(x * c, y * c)
|
||||||
operator fun XY.div(c: Double): XY = XY(x / c, y / c)
|
private operator fun XY.div(c: Double): XY = XY(x / c, y / c)
|
||||||
|
|
||||||
class Spring(
|
private class Spring(
|
||||||
context: Context,
|
context: Context,
|
||||||
val k: Double,
|
val k: Double,
|
||||||
val l0: Double,
|
val l0: Double,
|
||||||
@ -70,7 +70,7 @@ class Spring(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MaterialPoint(
|
private class MaterialPoint(
|
||||||
context: Context,
|
context: Context,
|
||||||
val mass: Double,
|
val mass: Double,
|
||||||
val force: DeviceState<XY>,
|
val force: DeviceState<XY>,
|
||||||
@ -94,7 +94,7 @@ class MaterialPoint(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class BodyOnSprings(
|
private class BodyOnSprings(
|
||||||
context: Context,
|
context: Context,
|
||||||
mass: Double,
|
mass: Double,
|
||||||
k: Double,
|
k: Double,
|
||||||
|
@ -72,7 +72,7 @@ class Modulator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val inertia = NumericalValue<Kilograms>(0.1)
|
private val mass = NumericalValue<Kilograms>(0.1)
|
||||||
|
|
||||||
private val leverage = NumericalValue<Meters>(0.05)
|
private val leverage = NumericalValue<Meters>(0.05)
|
||||||
|
|
||||||
@ -83,7 +83,13 @@ private val range = -6.0..6.0
|
|||||||
/**
|
/**
|
||||||
* The whole physical model is here
|
* The whole physical model is here
|
||||||
*/
|
*/
|
||||||
private fun createLinearDriveModel(context: Context, pidParameters: PidParameters): LinearDrive {
|
internal fun createLinearDriveModel(
|
||||||
|
context: Context,
|
||||||
|
pidParameters: PidParameters,
|
||||||
|
mass: NumericalValue<Kilograms>,
|
||||||
|
leverage: NumericalValue<Meters>,
|
||||||
|
position: MutableRangeState<NumericalValue<Meters>>,
|
||||||
|
): LinearDrive {
|
||||||
|
|
||||||
//create a drive model with zero starting force
|
//create a drive model with zero starting force
|
||||||
val drive = Drive(context)
|
val drive = Drive(context)
|
||||||
@ -91,8 +97,6 @@ private fun createLinearDriveModel(context: Context, pidParameters: PidParameter
|
|||||||
//a screw drive to converse a rotational moment into a linear one
|
//a screw drive to converse a rotational moment into a linear one
|
||||||
val screwDrive = ScrewDrive(context, leverage)
|
val screwDrive = ScrewDrive(context, leverage)
|
||||||
|
|
||||||
// Create a physical position coerced in a given range
|
|
||||||
val position = MutableRangeState<Meters>(0.0, range)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an inertia model.
|
* Create an inertia model.
|
||||||
@ -101,10 +105,10 @@ private fun createLinearDriveModel(context: Context, pidParameters: PidParameter
|
|||||||
* Force is the input parameter, position is output parameter
|
* Force is the input parameter, position is output parameter
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
val inertia = Inertia.linear(
|
val inertiaModel = Inertia.linear(
|
||||||
context = context,
|
context = context,
|
||||||
force = screwDrive.transformForce(drive.force),
|
force = screwDrive.transformForce(drive.force),
|
||||||
mass = inertia,
|
mass = mass,
|
||||||
position = position
|
position = position
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -114,12 +118,12 @@ private fun createLinearDriveModel(context: Context, pidParameters: PidParameter
|
|||||||
val startLimitSwitch = LimitSwitch(context, position.atStart)
|
val startLimitSwitch = LimitSwitch(context, position.atStart)
|
||||||
val endLimitSwitch = LimitSwitch(context, position.atEnd)
|
val endLimitSwitch = LimitSwitch(context, position.atEnd)
|
||||||
|
|
||||||
return context.install(
|
/**
|
||||||
"linearDrive",
|
* Install the resulting device
|
||||||
LinearDrive(drive, startLimitSwitch, endLimitSwitch, position, pidParameters)
|
*/
|
||||||
)
|
return LinearDrive(drive, startLimitSwitch, endLimitSwitch, position, pidParameters)
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private fun createModulator(linearDrive: LinearDrive): Modulator = linearDrive.context.install(
|
private fun createModulator(linearDrive: LinearDrive): Modulator = linearDrive.context.install(
|
||||||
"modulator",
|
"modulator",
|
||||||
@ -140,11 +144,21 @@ fun main() = application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val linearDrive: LinearDrive = remember {
|
val linearDrive: LinearDrive = remember {
|
||||||
createLinearDriveModel(context, pidParameters)
|
context.install(
|
||||||
|
"linearDrive",
|
||||||
|
createLinearDriveModel(
|
||||||
|
context = context,
|
||||||
|
pidParameters = pidParameters,
|
||||||
|
mass = mass,
|
||||||
|
leverage = leverage,
|
||||||
|
// Create a physical position coerced in a given range
|
||||||
|
position = MutableRangeState<Meters>(0.0, range)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val modulator = remember {
|
val modulator = remember {
|
||||||
createModulator(linearDrive)
|
context.install("modulator", createModulator(linearDrive))
|
||||||
}
|
}
|
||||||
|
|
||||||
//bind pid parameters
|
//bind pid parameters
|
||||||
|
@ -1,2 +1,174 @@
|
|||||||
package space.kscience.controls.demo.constructor
|
package space.kscience.controls.demo.constructor
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.application
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import space.kscience.controls.constructor.DeviceConstructor
|
||||||
|
import space.kscience.controls.constructor.MutableDeviceState
|
||||||
|
import space.kscience.controls.constructor.device
|
||||||
|
import space.kscience.controls.constructor.devices.StepDrive
|
||||||
|
import space.kscience.controls.constructor.devices.angle
|
||||||
|
import space.kscience.controls.constructor.models.RangeState
|
||||||
|
import space.kscience.controls.constructor.models.ScrewDrive
|
||||||
|
import space.kscience.controls.constructor.models.coerceIn
|
||||||
|
import space.kscience.controls.constructor.units.*
|
||||||
|
import space.kscience.controls.manager.ClockManager
|
||||||
|
import space.kscience.controls.manager.DeviceManager
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
import java.awt.Dimension
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
|
class Plotter(
|
||||||
|
context: Context,
|
||||||
|
xDrive: StepDrive,
|
||||||
|
yDrive: StepDrive,
|
||||||
|
val paint: suspend (Color) -> Unit,
|
||||||
|
) : DeviceConstructor(context) {
|
||||||
|
val xDrive by device(xDrive)
|
||||||
|
val yDrive by device(yDrive)
|
||||||
|
|
||||||
|
public fun moveToXY(x: Int, y: Int) {
|
||||||
|
xDrive.target.value = x
|
||||||
|
yDrive.target.value = y
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO add calibration
|
||||||
|
|
||||||
|
// TODO add draw as action
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Plotter.modernArt(xRange: IntRange, yRange: IntRange) {
|
||||||
|
while (isActive){
|
||||||
|
val randomX = Random.nextInt(xRange.first, xRange.last)
|
||||||
|
val randomY = Random.nextInt(xRange.first, xRange.last)
|
||||||
|
moveToXY(randomX, randomY)
|
||||||
|
delay(500)
|
||||||
|
paint(Color(Random.nextInt()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Plotter.square(xRange: IntRange, yRange: IntRange) {
|
||||||
|
while (isActive) {
|
||||||
|
moveToXY(xRange.first, yRange.first)
|
||||||
|
delay(1000)
|
||||||
|
paint(Color.Red)
|
||||||
|
|
||||||
|
moveToXY(xRange.first, yRange.last)
|
||||||
|
delay(1000)
|
||||||
|
paint(Color.Red)
|
||||||
|
|
||||||
|
moveToXY(xRange.last, yRange.last)
|
||||||
|
delay(1000)
|
||||||
|
paint(Color.Red)
|
||||||
|
|
||||||
|
moveToXY(xRange.last, yRange.first)
|
||||||
|
delay(1000)
|
||||||
|
paint(Color.Red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val xRange = NumericalValue<Meters>(-0.5)..NumericalValue<Meters>(0.5)
|
||||||
|
private val yRange = NumericalValue<Meters>(-0.5)..NumericalValue<Meters>(0.5)
|
||||||
|
private val ticksPerSecond = MutableDeviceState(250.0)
|
||||||
|
private val step = NumericalValue<Degrees>(1.2)
|
||||||
|
|
||||||
|
|
||||||
|
private data class PlotterPoint(
|
||||||
|
val x: NumericalValue<Meters>,
|
||||||
|
val y: NumericalValue<Meters>,
|
||||||
|
val color: Color = Color.Black,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun main() = application {
|
||||||
|
Window(title = "Pid regulator simulator", onCloseRequest = ::exitApplication) {
|
||||||
|
window.minimumSize = Dimension(400, 400)
|
||||||
|
|
||||||
|
val points = remember { mutableStateListOf<PlotterPoint>() }
|
||||||
|
var position by remember { mutableStateOf(PlotterPoint(NumericalValue(0), NumericalValue(0))) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
val context = Context {
|
||||||
|
plugin(DeviceManager)
|
||||||
|
plugin(ClockManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Here goes the device definition block */
|
||||||
|
|
||||||
|
|
||||||
|
val xScrewDrive = ScrewDrive(context, NumericalValue(0.01))
|
||||||
|
val xDrive = StepDrive(context, ticksPerSecond)
|
||||||
|
val x: RangeState<NumericalValue<Meters>> = xScrewDrive.transformOffset(xDrive.angle(step)).coerceIn(xRange)
|
||||||
|
|
||||||
|
val yScrewDrive = ScrewDrive(context, NumericalValue(0.01))
|
||||||
|
val yDrive = StepDrive(context, ticksPerSecond)
|
||||||
|
val y: RangeState<NumericalValue<Meters>> = yScrewDrive.transformOffset(yDrive.angle(step)).coerceIn(yRange)
|
||||||
|
|
||||||
|
val plotter = Plotter(context, xDrive, yDrive) { color ->
|
||||||
|
println("Point X: ${x.value.value}, Y: ${y.value.value}, color: $color")
|
||||||
|
points.add(PlotterPoint(x.value, y.value, color))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Start visualization program */
|
||||||
|
|
||||||
|
launch {
|
||||||
|
x.valueFlow.collect {
|
||||||
|
position = position.copy(x = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
y.valueFlow.collect {
|
||||||
|
position = position.copy(y = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
val range = -100..100
|
||||||
|
plotter.modernArt(range, range)
|
||||||
|
//plotter.square(range, range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Here goes the visualization block */
|
||||||
|
|
||||||
|
MaterialTheme {
|
||||||
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
|
fun toOffset(x: NumericalValue<Meters>, y: NumericalValue<Meters>): Offset {
|
||||||
|
val canvasX = (x - xRange.start) / (xRange.endInclusive - xRange.start) * size.width
|
||||||
|
val canvasY = (y - yRange.start) / (yRange.endInclusive - yRange.start) * size.height
|
||||||
|
return Offset(canvasX.toFloat(), canvasY.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
val center = toOffset(position.x, position.y)
|
||||||
|
|
||||||
|
|
||||||
|
drawRect(
|
||||||
|
Color.LightGray,
|
||||||
|
topLeft = Offset(0f, center.y - 5f),
|
||||||
|
size = Size(size.width, 10f)
|
||||||
|
)
|
||||||
|
|
||||||
|
drawCircle(Color.Black, radius = 10f, center = center)
|
||||||
|
|
||||||
|
|
||||||
|
points.forEach {
|
||||||
|
drawCircle(it.color, radius = 2f, center = toOffset(it.x, it.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user