Finish plotter demo

This commit is contained in:
Alexander Nozik 2024-06-03 15:38:39 +03:00
parent a2b7d1ecb0
commit 4a5f5fab8c
8 changed files with 53 additions and 84 deletions

View File

@ -9,6 +9,7 @@ import space.kscience.controls.constructor.units.numerical
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.MetaConverter
//TODO use current as input
public class Drive(
context: Context,

View File

@ -9,7 +9,7 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.MetaConverter
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.math.roundToLong
import kotlin.time.DurationUnit
/**
@ -22,25 +22,27 @@ import kotlin.time.DurationUnit
public class StepDrive(
context: Context,
ticksPerSecond: MutableDeviceState<Double>,
target: MutableDeviceState<Int> = MutableDeviceState(0),
private val writeTicks: suspend (ticks: Int, speed: Double) -> Unit = { _, _ -> },
target: MutableDeviceState<Long> = MutableDeviceState(0),
private val writeTicks: suspend (ticks: Long, speed: Double) -> Unit = { _, _ -> },
) : DeviceConstructor(context) {
public val target: MutableDeviceState<Int> by property(MetaConverter.int, target)
public val target: MutableDeviceState<Long> by property(MetaConverter.long, target)
public val speed: MutableDeviceState<Double> by property(MetaConverter.double, ticksPerSecond)
private val positionState = stateOf(target.value)
public val position: DeviceState<Int> by property(MetaConverter.int, positionState)
public val position: DeviceState<Long> by property(MetaConverter.long, positionState)
//FIXME round to zero problem
private val ticker = onTimer(reads = setOf(target, position), writes = setOf(position)) { prev, next ->
val tickSpeed = ticksPerSecond.value
val timeDelta = (next - prev).toDouble(DurationUnit.SECONDS)
val ticksDelta: Int = target.value - position.value
val steps: Int = when {
ticksDelta > 0 -> min(ticksDelta, (timeDelta * tickSpeed).roundToInt())
ticksDelta < 0 -> max(ticksDelta, -(timeDelta * tickSpeed).roundToInt())
val ticksDelta: Long = target.value - position.value
val steps: Long = when {
ticksDelta > 0 -> min(ticksDelta, (timeDelta * tickSpeed).roundToLong())
ticksDelta < 0 -> max(ticksDelta, -(timeDelta * tickSpeed).roundToLong())
else -> return@onTimer
}
writeTicks(steps, tickSpeed)
@ -55,6 +57,6 @@ public fun StepDrive.angle(
step: NumericalValue<Degrees>,
zero: NumericalValue<Degrees> = NumericalValue(0),
): DeviceState<NumericalValue<Degrees>> = position.map {
zero + it.toDouble() * step
zero + it * step
}

View File

@ -25,11 +25,9 @@ public class Inertia<U : UnitsOfMeasurement, V : UnitsOfMeasurement>(
registerState(velocity)
}
private val movementTimer = timer(timerPrecision)
private var currentForce = force.value
private val movement = movementTimer.onChange { prev, next ->
private val movement = onTimer (timerPrecision) { prev, next ->
val dtSeconds = (next - prev).toDouble(DurationUnit.SECONDS)
// compute new value based on velocity and acceleration from the previous step
@ -57,21 +55,6 @@ public class Inertia<U : UnitsOfMeasurement, V : UnitsOfMeasurement>(
position = position,
velocity = velocity
)
//
//
// public fun linear(
// context: Context,
// force: DeviceState<NumericalValue<Newtons>>,
// mass: NumericalValue<Kilograms>,
// initialPosition: NumericalValue<Meters>,
// initialVelocity: NumericalValue<MetersPerSecond> = NumericalValue(0),
// ): Inertia<Meters, MetersPerSecond> = Inertia(
// context = context,
// force = force.values(),
// inertia = mass.value,
// position = MutableDeviceState(initialPosition),
// velocity = MutableDeviceState(initialVelocity)
// )
public fun circular(
context: Context,
@ -86,19 +69,5 @@ public class Inertia<U : UnitsOfMeasurement, V : UnitsOfMeasurement>(
position = position,
velocity = velocity
)
//
// public fun circular(
// context: Context,
// force: DeviceState<NumericalValue<NewtonsMeters>>,
// momentOfInertia: NumericalValue<KgM2>,
// initialPosition: NumericalValue<Degrees>,
// initialVelocity: NumericalValue<DegreesPerSecond> = NumericalValue(0),
// ): Inertia<Degrees, DegreesPerSecond> = Inertia(
// context = context,
// force = force.values(),
// inertia = momentOfInertia.value,
// position = MutableDeviceState(initialPosition),
// velocity = MutableDeviceState(initialVelocity)
// )
}
}

View File

@ -12,17 +12,17 @@ public class ScrewDrive(
public val leverage: NumericalValue<Meters>,
) : ModelConstructor(context) {
public fun transformForce(
stateOfForce: DeviceState<NumericalValue<NewtonsMeters>>,
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfForce) {
NumericalValue(it.value * leverage.value/2/ PI)
public fun torqueToForce(
stateOfMomentum: DeviceState<NumericalValue<NewtonsMeters>>,
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfMomentum) { momentum ->
NumericalValue(momentum.value / leverage.value )
}
public fun transformOffset(
public fun degreesToMeters(
stateOfAngle: DeviceState<NumericalValue<Degrees>>,
offset: NumericalValue<Meters> = NumericalValue(0),
): DeviceState<NumericalValue<Meters>> = DeviceState.map(stateOfAngle) {
offset + NumericalValue(it.value * leverage.value/2/ PI)
): DeviceState<NumericalValue<Meters>> = DeviceState.map(stateOfAngle) { degrees ->
offset + NumericalValue(degrees.value * 2 * PI / 360 *leverage.value )
}
}

View File

@ -1,17 +0,0 @@
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)
}

View File

@ -0,0 +1,8 @@
package space.kscience.controls.constructor.models
import space.kscience.controls.constructor.units.NumericalValue
import space.kscience.controls.constructor.units.UnitsOfMeasurement
public data class XY<U: UnitsOfMeasurement>(val x: NumericalValue<U>, val y: NumericalValue<U>)
public data class XYZ<U: UnitsOfMeasurement>(val x: NumericalValue<U>, val y: NumericalValue<U>, val z: NumericalValue<U>)

View File

@ -94,7 +94,7 @@ internal fun createLinearDriveModel(
//create a drive model with zero starting force
val drive = Drive(context)
//a screw drive to converse a rotational moment into a linear one
//a screw drive to convert a rotational moment into a force
val screwDrive = ScrewDrive(context, leverage)
@ -107,7 +107,7 @@ internal fun createLinearDriveModel(
*/
val inertiaModel = Inertia.linear(
context = context,
force = screwDrive.transformForce(drive.force),
force = screwDrive.torqueToForce(drive.force),
mass = mass,
position = position
)
@ -266,7 +266,7 @@ fun main() = application {
yAxisModel = rememberDoubleLinearAxisModel((range.start - 1.0)..(range.endInclusive + 1.0)),
xAxisTitle = { Text("Time in seconds relative to current") },
xAxisLabels = { it: Instant ->
androidx.compose.material3.Text(
Text(
(clock.now() - it).toDouble(
DurationUnit.SECONDS
).toString(2)

View File

@ -18,7 +18,6 @@ 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.*
@ -38,11 +37,15 @@ class Plotter(
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
public fun moveToXY(x: Number, y: Number) {
xDrive.target.value = x.toLong()
yDrive.target.value = y.toLong()
}
// val position = combineState(xDrive.position, yDrive.position) { x, y ->
// space.kscience.controls.constructor.models.XY(x, y)
// }
//TODO add calibration
// TODO add draw as action
@ -51,8 +54,9 @@ class Plotter(
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)
val randomY = Random.nextInt(yRange.first, yRange.last)
moveToXY(randomX, randomY)
//TODO wait for position instead of custom delay
delay(500)
paint(Color(Random.nextInt()))
}
@ -80,8 +84,8 @@ suspend fun Plotter.square(xRange: IntRange, yRange: IntRange) {
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 val ticksPerSecond = MutableDeviceState(3000.0)
private val step = NumericalValue<Degrees>(1.8)
private data class PlotterPoint(
@ -106,13 +110,13 @@ suspend fun main() = application {
/* 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 xTransmission = ScrewDrive(context, NumericalValue(0.01))
val x = xTransmission.degreesToMeters(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 yTransmission = ScrewDrive(context, NumericalValue(0.01))
val y = yTransmission.degreesToMeters(yDrive.angle(step)).coerceIn(yRange)
val plotter = Plotter(context, xDrive, yDrive) { color ->
println("Point X: ${x.value.value}, Y: ${y.value.value}, color: $color")
@ -134,10 +138,12 @@ suspend fun main() = application {
}
}
/* run program */
launch {
val range = -100..100
plotter.modernArt(range, range)
//plotter.square(range, range)
val range = -1000..1000
// plotter.modernArt(range, range)
plotter.square(range, range)
}
}