Finish plotter demo
This commit is contained in:
parent
a2b7d1ecb0
commit
4a5f5fab8c
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 )
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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)
|
|
||||||
}
|
|
@ -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>)
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user