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.context.Context
import space.kscience.dataforge.meta.MetaConverter import space.kscience.dataforge.meta.MetaConverter
//TODO use current as input
public class Drive( public class Drive(
context: Context, context: Context,

View File

@ -9,7 +9,7 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.MetaConverter import space.kscience.dataforge.meta.MetaConverter
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToLong
import kotlin.time.DurationUnit import kotlin.time.DurationUnit
/** /**
@ -22,25 +22,27 @@ import kotlin.time.DurationUnit
public class StepDrive( public class StepDrive(
context: Context, context: Context,
ticksPerSecond: MutableDeviceState<Double>, ticksPerSecond: MutableDeviceState<Double>,
target: MutableDeviceState<Int> = MutableDeviceState(0), target: MutableDeviceState<Long> = MutableDeviceState(0),
private val writeTicks: suspend (ticks: Int, speed: Double) -> Unit = { _, _ -> }, private val writeTicks: suspend (ticks: Long, speed: Double) -> Unit = { _, _ -> },
) : DeviceConstructor(context) { ) : 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) 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<Long> by property(MetaConverter.long, positionState)
//FIXME round to zero problem
private val ticker = onTimer(reads = setOf(target, position), writes = setOf(position)) { prev, next -> private val ticker = onTimer(reads = setOf(target, position), writes = setOf(position)) { prev, next ->
val tickSpeed = ticksPerSecond.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: Long = target.value - position.value
val steps: Int = when { val steps: Long = when {
ticksDelta > 0 -> min(ticksDelta, (timeDelta * tickSpeed).roundToInt()) ticksDelta > 0 -> min(ticksDelta, (timeDelta * tickSpeed).roundToLong())
ticksDelta < 0 -> max(ticksDelta, -(timeDelta * tickSpeed).roundToInt()) ticksDelta < 0 -> max(ticksDelta, -(timeDelta * tickSpeed).roundToLong())
else -> return@onTimer else -> return@onTimer
} }
writeTicks(steps, tickSpeed) writeTicks(steps, tickSpeed)
@ -55,6 +57,6 @@ public fun StepDrive.angle(
step: NumericalValue<Degrees>, step: NumericalValue<Degrees>,
zero: NumericalValue<Degrees> = NumericalValue(0), zero: NumericalValue<Degrees> = NumericalValue(0),
): DeviceState<NumericalValue<Degrees>> = position.map { ): 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) registerState(velocity)
} }
private val movementTimer = timer(timerPrecision)
private var currentForce = force.value 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) val dtSeconds = (next - prev).toDouble(DurationUnit.SECONDS)
// compute new value based on velocity and acceleration from the previous step // 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, position = position,
velocity = velocity 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( public fun circular(
context: Context, context: Context,
@ -86,19 +69,5 @@ public class Inertia<U : UnitsOfMeasurement, V : UnitsOfMeasurement>(
position = position, position = position,
velocity = velocity 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>, public val leverage: NumericalValue<Meters>,
) : ModelConstructor(context) { ) : ModelConstructor(context) {
public fun transformForce( public fun torqueToForce(
stateOfForce: DeviceState<NumericalValue<NewtonsMeters>>, stateOfMomentum: DeviceState<NumericalValue<NewtonsMeters>>,
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfForce) { ): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfMomentum) { momentum ->
NumericalValue(it.value * leverage.value/2/ PI) NumericalValue(momentum.value / leverage.value )
} }
public fun transformOffset( public fun degreesToMeters(
stateOfAngle: DeviceState<NumericalValue<Degrees>>, stateOfAngle: DeviceState<NumericalValue<Degrees>>,
offset: NumericalValue<Meters> = NumericalValue(0), offset: NumericalValue<Meters> = NumericalValue(0),
): DeviceState<NumericalValue<Meters>> = DeviceState.map(stateOfAngle) { ): DeviceState<NumericalValue<Meters>> = DeviceState.map(stateOfAngle) { degrees ->
offset + NumericalValue(it.value * leverage.value/2/ PI) 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 //create a drive model with zero starting force
val drive = Drive(context) 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) val screwDrive = ScrewDrive(context, leverage)
@ -107,7 +107,7 @@ internal fun createLinearDriveModel(
*/ */
val inertiaModel = Inertia.linear( val inertiaModel = Inertia.linear(
context = context, context = context,
force = screwDrive.transformForce(drive.force), force = screwDrive.torqueToForce(drive.force),
mass = mass, mass = mass,
position = position position = position
) )
@ -266,7 +266,7 @@ fun main() = application {
yAxisModel = rememberDoubleLinearAxisModel((range.start - 1.0)..(range.endInclusive + 1.0)), yAxisModel = rememberDoubleLinearAxisModel((range.start - 1.0)..(range.endInclusive + 1.0)),
xAxisTitle = { Text("Time in seconds relative to current") }, xAxisTitle = { Text("Time in seconds relative to current") },
xAxisLabels = { it: Instant -> xAxisLabels = { it: Instant ->
androidx.compose.material3.Text( Text(
(clock.now() - it).toDouble( (clock.now() - it).toDouble(
DurationUnit.SECONDS DurationUnit.SECONDS
).toString(2) ).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.device
import space.kscience.controls.constructor.devices.StepDrive import space.kscience.controls.constructor.devices.StepDrive
import space.kscience.controls.constructor.devices.angle 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.ScrewDrive
import space.kscience.controls.constructor.models.coerceIn import space.kscience.controls.constructor.models.coerceIn
import space.kscience.controls.constructor.units.* import space.kscience.controls.constructor.units.*
@ -38,21 +37,26 @@ class Plotter(
val xDrive by device(xDrive) val xDrive by device(xDrive)
val yDrive by device(yDrive) val yDrive by device(yDrive)
public fun moveToXY(x: Int, y: Int) { public fun moveToXY(x: Number, y: Number) {
xDrive.target.value = x xDrive.target.value = x.toLong()
yDrive.target.value = y 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 calibration
// TODO add draw as action // TODO add draw as action
} }
suspend fun Plotter.modernArt(xRange: IntRange, yRange: IntRange) { suspend fun Plotter.modernArt(xRange: IntRange, yRange: IntRange) {
while (isActive){ while (isActive) {
val randomX = Random.nextInt(xRange.first, xRange.last) 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) moveToXY(randomX, randomY)
//TODO wait for position instead of custom delay
delay(500) delay(500)
paint(Color(Random.nextInt())) 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 xRange = NumericalValue<Meters>(-0.5)..NumericalValue<Meters>(0.5)
private val yRange = 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 ticksPerSecond = MutableDeviceState(3000.0)
private val step = NumericalValue<Degrees>(1.2) private val step = NumericalValue<Degrees>(1.8)
private data class PlotterPoint( private data class PlotterPoint(
@ -106,13 +110,13 @@ suspend fun main() = application {
/* Here goes the device definition block */ /* Here goes the device definition block */
val xScrewDrive = ScrewDrive(context, NumericalValue(0.01))
val xDrive = StepDrive(context, ticksPerSecond) 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 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 -> val plotter = Plotter(context, xDrive, yDrive) { color ->
println("Point X: ${x.value.value}, Y: ${y.value.value}, color: $color") println("Point X: ${x.value.value}, Y: ${y.value.value}, color: $color")
@ -134,10 +138,12 @@ suspend fun main() = application {
} }
} }
/* run program */
launch { launch {
val range = -100..100 val range = -1000..1000
plotter.modernArt(range, range) // plotter.modernArt(range, range)
//plotter.square(range, range) plotter.square(range, range)
} }
} }