Fix PID demo
This commit is contained in:
parent
91f860adf6
commit
c63c2db651
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
.idea/
|
||||
.gradle
|
||||
.kotlin
|
||||
|
||||
*.iws
|
||||
*.iml
|
||||
|
@ -24,7 +24,7 @@ public class Inertia<U : UnitsOfMeasurement, V : UnitsOfMeasurement>(
|
||||
|
||||
private var currentForce = force.value
|
||||
|
||||
private val movement = onTimer { prev, next ->
|
||||
private val movement = onTimer(DefaultTimer.REALTIME) { prev, next ->
|
||||
val dtSeconds = (next - prev).toDouble(DurationUnit.SECONDS)
|
||||
|
||||
// compute new value based on velocity and acceleration from the previous step
|
||||
|
@ -7,22 +7,25 @@ import space.kscience.controls.constructor.units.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import kotlin.math.PI
|
||||
|
||||
public class ScrewDrive(
|
||||
/**
|
||||
* https://en.wikipedia.org/wiki/Leadscrew
|
||||
*/
|
||||
public class Leadscrew(
|
||||
context: Context,
|
||||
public val leverage: NumericalValue<Meters>,
|
||||
) : ModelConstructor(context) {
|
||||
|
||||
public fun torqueToForce(
|
||||
stateOfMomentum: DeviceState<NumericalValue<NewtonsMeters>>,
|
||||
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfMomentum) { momentum ->
|
||||
NumericalValue(momentum.value / leverage.value )
|
||||
stateOfTorque: DeviceState<NumericalValue<NewtonsMeters>>,
|
||||
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfTorque) { torque ->
|
||||
NumericalValue(torque.value / leverage.value )
|
||||
}
|
||||
|
||||
public fun degreesToMeters(
|
||||
stateOfAngle: DeviceState<NumericalValue<Degrees>>,
|
||||
offset: NumericalValue<Meters> = NumericalValue(0),
|
||||
): DeviceState<NumericalValue<Meters>> = DeviceState.map(stateOfAngle) { degrees ->
|
||||
offset + NumericalValue(degrees.value * 2 * PI / 360 *leverage.value )
|
||||
offset + NumericalValue(degrees.value * 2 * PI / 360 * leverage.value )
|
||||
}
|
||||
|
||||
}
|
@ -3,12 +3,20 @@ package space.kscience.controls.vision
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import kotlinx.serialization.modules.subclass
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
|
||||
public expect class ControlVisionPlugin: VisionPlugin{
|
||||
public companion object: PluginFactory<ControlVisionPlugin>
|
||||
override val tag: PluginTag
|
||||
override val visionSerializersModule: SerializersModule
|
||||
public companion object: PluginFactory<ControlVisionPlugin>{
|
||||
override val tag: PluginTag
|
||||
override fun build(context: Context, meta: Meta): ControlVisionPlugin
|
||||
}
|
||||
}
|
||||
|
||||
internal val controlsVisionSerializersModule = SerializersModule {
|
||||
|
@ -158,7 +158,7 @@ public fun <T> Plot.plotDeviceState(
|
||||
|
||||
public fun Plot.plotNumberState(
|
||||
context: Context,
|
||||
state: DeviceState<out Number>,
|
||||
state: DeviceState<Number>,
|
||||
maxAge: Duration = defaultMaxAge,
|
||||
maxPoints: Int = defaultMaxPoints,
|
||||
minPoints: Int = defaultMinPoints,
|
||||
|
@ -43,9 +43,9 @@ private val sliderRenderer = ElementVisionRenderer<SliderVision> { name, vision:
|
||||
|
||||
|
||||
public actual class ControlVisionPlugin : VisionPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
actual override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override val visionSerializersModule: SerializersModule get() = controlsVisionSerializersModule
|
||||
actual override val visionSerializersModule: SerializersModule get() = controlsVisionSerializersModule
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = when (target) {
|
||||
ElementVisionRenderer.TYPE -> mapOf(
|
||||
@ -57,9 +57,9 @@ public actual class ControlVisionPlugin : VisionPlugin() {
|
||||
}
|
||||
|
||||
public actual companion object : PluginFactory<ControlVisionPlugin> {
|
||||
override val tag: PluginTag = PluginTag("controls.vision")
|
||||
actual override val tag: PluginTag = PluginTag("controls.vision")
|
||||
|
||||
override fun build(context: Context, meta: Meta): ControlVisionPlugin = ControlVisionPlugin()
|
||||
actual override fun build(context: Context, meta: Meta): ControlVisionPlugin = ControlVisionPlugin()
|
||||
|
||||
}
|
||||
}
|
@ -8,14 +8,14 @@ import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
|
||||
public actual class ControlVisionPlugin : VisionPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
actual override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override val visionSerializersModule: SerializersModule get() = controlsVisionSerializersModule
|
||||
actual override val visionSerializersModule: SerializersModule get() = controlsVisionSerializersModule
|
||||
|
||||
public actual companion object : PluginFactory<ControlVisionPlugin> {
|
||||
override val tag: PluginTag = PluginTag("controls.vision")
|
||||
actual override val tag: PluginTag = PluginTag("controls.vision")
|
||||
|
||||
override fun build(context: Context, meta: Meta): ControlVisionPlugin = ControlVisionPlugin()
|
||||
actual override fun build(context: Context, meta: Meta): ControlVisionPlugin = ControlVisionPlugin()
|
||||
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@ import org.jetbrains.compose.ExperimentalComposeLibrary
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
alias(spclibs.plugins.compose)
|
||||
alias(spclibs.plugins.compose.compiler)
|
||||
alias(spclibs.plugins.compose.jb)
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@ package space.kscience.controls.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -30,7 +30,7 @@ public fun NumberTextField(
|
||||
Row (verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
|
||||
step.takeIf { it > 0.0 }?.let {
|
||||
IconButton({ onValueChange(value.toDouble() - step) }, enabled = enabled) {
|
||||
Icon(Icons.Default.KeyboardArrowLeft, "decrease value")
|
||||
Icon(Icons.AutoMirrored.Filled.KeyboardArrowLeft, "decrease value")
|
||||
}
|
||||
}
|
||||
TextField(
|
||||
@ -52,7 +52,7 @@ public fun NumberTextField(
|
||||
)
|
||||
step.takeIf { it > 0.0 }?.let {
|
||||
IconButton({ onValueChange(value.toDouble() + step) }, enabled = enabled) {
|
||||
Icon(Icons.Default.KeyboardArrowRight, "increase value")
|
||||
Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, "increase value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
alias(spclibs.plugins.compose)
|
||||
alias(spclibs.plugins.compose.compiler)
|
||||
alias(spclibs.plugins.compose.jb)
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,7 +3,8 @@ import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
alias(spclibs.plugins.compose)
|
||||
alias(spclibs.plugins.compose.compiler)
|
||||
alias(spclibs.plugins.compose.jb)
|
||||
}
|
||||
|
||||
kscience {
|
||||
|
@ -85,7 +85,7 @@ private class BodyOnSprings(
|
||||
}
|
||||
|
||||
fun main() = application {
|
||||
val initialState = XYZ<Meters>(0, 0.4, 0)
|
||||
val initialState = XYZ<Meters>(0.05, 0.4, 0)
|
||||
|
||||
Window(title = "Ball on springs", onCloseRequest = ::exitApplication) {
|
||||
window.minimumSize = Dimension(400, 400)
|
||||
|
@ -32,10 +32,10 @@ import space.kscience.controls.constructor.devices.Drive
|
||||
import space.kscience.controls.constructor.devices.LimitSwitch
|
||||
import space.kscience.controls.constructor.devices.LinearDrive
|
||||
import space.kscience.controls.constructor.models.Inertia
|
||||
import space.kscience.controls.constructor.models.Leadscrew
|
||||
import space.kscience.controls.constructor.models.MutableRangeState
|
||||
import space.kscience.controls.constructor.models.PidParameters
|
||||
import space.kscience.controls.constructor.models.ScrewDrive
|
||||
import space.kscience.controls.constructor.timer
|
||||
import space.kscience.controls.constructor.onTimer
|
||||
import space.kscience.controls.constructor.units.Kilograms
|
||||
import space.kscience.controls.constructor.units.Meters
|
||||
import space.kscience.controls.constructor.units.NumericalValue
|
||||
@ -56,13 +56,13 @@ import kotlin.time.DurationUnit
|
||||
class Modulator(
|
||||
context: Context,
|
||||
target: MutableDeviceState<NumericalValue<Meters>>,
|
||||
var freq: Double = 0.1,
|
||||
var timeStep: Duration = 5.milliseconds,
|
||||
var freq: Double = 0.1,
|
||||
) : DeviceConstructor(context) {
|
||||
private val clockStart = clock.now()
|
||||
|
||||
private val modulation = timer(10.milliseconds).onNext {
|
||||
val timeFromStart = clock.now() - clockStart
|
||||
private val modulation = onTimer(timeStep) { _, next ->
|
||||
val timeFromStart = next - clockStart
|
||||
val t = timeFromStart.toDouble(DurationUnit.SECONDS)
|
||||
target.value = NumericalValue(
|
||||
5 * sin(2.0 * PI * freq * t) +
|
||||
@ -72,9 +72,9 @@ class Modulator(
|
||||
}
|
||||
|
||||
|
||||
private val mass = NumericalValue<Kilograms>(0.1)
|
||||
private val mass = NumericalValue<Kilograms>(1)
|
||||
|
||||
private val leverage = NumericalValue<Meters>(0.05)
|
||||
private val leverage = NumericalValue<Meters>(1.0)
|
||||
|
||||
private val maxAge = 10.seconds
|
||||
|
||||
@ -95,7 +95,7 @@ internal fun createLinearDriveModel(
|
||||
val drive = Drive(context)
|
||||
|
||||
//a screw drive to convert a rotational moment into a force
|
||||
val screwDrive = ScrewDrive(context, leverage)
|
||||
val leadscrew = Leadscrew(context, leverage)
|
||||
|
||||
|
||||
/**
|
||||
@ -107,7 +107,7 @@ internal fun createLinearDriveModel(
|
||||
*/
|
||||
val inertiaModel = Inertia.linear(
|
||||
context = context,
|
||||
force = screwDrive.torqueToForce(drive.force),
|
||||
force = leadscrew.torqueToForce(drive.force),
|
||||
mass = mass,
|
||||
position = position
|
||||
)
|
||||
@ -130,6 +130,8 @@ private fun createModulator(linearDrive: LinearDrive): Modulator = linearDrive.c
|
||||
Modulator(linearDrive.context, linearDrive.pid.target)
|
||||
)
|
||||
|
||||
private val startPid = PidParameters(kp = 250.0, ki = 0.0, kd = -20.0, timeStep = 20.milliseconds)
|
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class, ExperimentalKoalaPlotApi::class)
|
||||
fun main() = application {
|
||||
val context = remember {
|
||||
@ -140,7 +142,7 @@ fun main() = application {
|
||||
}
|
||||
|
||||
var pidParameters by remember {
|
||||
mutableStateOf(PidParameters(kp = 900.0, ki = 20.0, kd = -50.0, timeStep = 0.005.seconds))
|
||||
mutableStateOf(startPid)
|
||||
}
|
||||
|
||||
val linearDrive: LinearDrive = remember {
|
||||
@ -183,14 +185,14 @@ fun main() = application {
|
||||
NumberTextField(
|
||||
value = pidParameters.kp,
|
||||
onValueChange = { pidParameters = pidParameters.copy(kp = it.toDouble()) },
|
||||
formatter = { String.format("%.2f", it.toDouble()) },
|
||||
step = 1.0,
|
||||
formatter = { String.format("%.3f", it.toDouble()) },
|
||||
step = 0.01,
|
||||
modifier = Modifier.width(200.dp),
|
||||
)
|
||||
Slider(
|
||||
pidParameters.kp.toFloat(),
|
||||
{ pidParameters = pidParameters.copy(kp = it.toDouble()) },
|
||||
valueRange = 0f..1000f,
|
||||
valueRange = 0f..100f,
|
||||
steps = 100
|
||||
)
|
||||
}
|
||||
@ -199,15 +201,15 @@ fun main() = application {
|
||||
NumberTextField(
|
||||
value = pidParameters.ki,
|
||||
onValueChange = { pidParameters = pidParameters.copy(ki = it.toDouble()) },
|
||||
formatter = { String.format("%.2f", it.toDouble()) },
|
||||
step = 0.1,
|
||||
formatter = { String.format("%.3f", it.toDouble()) },
|
||||
step = 0.01,
|
||||
modifier = Modifier.width(200.dp),
|
||||
)
|
||||
|
||||
Slider(
|
||||
pidParameters.ki.toFloat(),
|
||||
{ pidParameters = pidParameters.copy(ki = it.toDouble()) },
|
||||
valueRange = -100f..100f,
|
||||
valueRange = -10f..10f,
|
||||
steps = 100
|
||||
)
|
||||
}
|
||||
@ -216,15 +218,15 @@ fun main() = application {
|
||||
NumberTextField(
|
||||
value = pidParameters.kd,
|
||||
onValueChange = { pidParameters = pidParameters.copy(kd = it.toDouble()) },
|
||||
formatter = { String.format("%.2f", it.toDouble()) },
|
||||
step = 0.1,
|
||||
formatter = { String.format("%.3f", it.toDouble()) },
|
||||
step = 0.01,
|
||||
modifier = Modifier.width(200.dp),
|
||||
)
|
||||
|
||||
Slider(
|
||||
pidParameters.kd.toFloat(),
|
||||
{ pidParameters = pidParameters.copy(kd = it.toDouble()) },
|
||||
valueRange = -100f..100f,
|
||||
valueRange = -10f..10f,
|
||||
steps = 100
|
||||
)
|
||||
}
|
||||
@ -247,12 +249,7 @@ fun main() = application {
|
||||
}
|
||||
Row {
|
||||
Button({
|
||||
pidParameters = PidParameters(
|
||||
kp = 2.5,
|
||||
ki = 0.0,
|
||||
kd = -0.1,
|
||||
timeStep = 0.005.seconds
|
||||
)
|
||||
pidParameters = startPid
|
||||
}) {
|
||||
Text("Reset")
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import kotlinx.coroutines.isActive
|
||||
import space.kscience.controls.constructor.*
|
||||
import space.kscience.controls.constructor.devices.StepDrive
|
||||
import space.kscience.controls.constructor.devices.angle
|
||||
import space.kscience.controls.constructor.models.ScrewDrive
|
||||
import space.kscience.controls.constructor.models.Leadscrew
|
||||
import space.kscience.controls.constructor.models.coerceIn
|
||||
import space.kscience.controls.constructor.units.*
|
||||
import space.kscience.controls.manager.ClockManager
|
||||
@ -99,11 +99,11 @@ private class PlotterModel(
|
||||
) : ModelConstructor(context) {
|
||||
|
||||
private val xDrive = StepDrive(context, ticksPerSecond)
|
||||
private val xTransmission = ScrewDrive(context, NumericalValue(0.01))
|
||||
private val xTransmission = Leadscrew(context, NumericalValue(0.01))
|
||||
val x = xTransmission.degreesToMeters(xDrive.angle(step)).coerceIn(xRange)
|
||||
|
||||
private val yDrive = StepDrive(context, ticksPerSecond)
|
||||
private val yTransmission = ScrewDrive(context, NumericalValue(0.01))
|
||||
private val yTransmission = Leadscrew(context, NumericalValue(0.01))
|
||||
val y = yTransmission.degreesToMeters(yDrive.angle(step)).coerceIn(yRange)
|
||||
|
||||
val xy: DeviceState<XY<Meters>> = combineState(x, y) { x, y -> XY(x, y) }
|
||||
@ -143,7 +143,7 @@ suspend fun main() = application {
|
||||
|
||||
|
||||
val range = -1000..1000
|
||||
// plotter.modernArt(range, range)
|
||||
// plotterModel.plotter.modernArt(range, range)
|
||||
plotterModel.plotter.square(range, range)
|
||||
|
||||
}
|
||||
|
41
demo/devices-on-map/build.gradle.kts
Normal file
41
demo/devices-on-map/build.gradle.kts
Normal file
@ -0,0 +1,41 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
alias(spclibs.plugins.compose.compiler)
|
||||
alias(spclibs.plugins.compose.jb)
|
||||
}
|
||||
|
||||
kscience {
|
||||
jvm()
|
||||
useSerialization()
|
||||
useContextReceivers()
|
||||
commonMain {
|
||||
implementation(projects.controlsVisualisationCompose)
|
||||
implementation(projects.controlsConstructor)
|
||||
}
|
||||
jvmMain {
|
||||
// implementation("io.ktor:ktor-server-cio")
|
||||
implementation(spclibs.logback.classic)
|
||||
implementation(libs.sciprog.maps.compose)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin.explicitApi = ExplicitApiMode.Disabled
|
||||
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "space.kscience.controls.demo.map.MainKt"
|
||||
}
|
||||
}
|
8
demo/devices-on-map/src/jvmMain/kotlin/main.kt
Normal file
8
demo/devices-on-map/src/jvmMain/kotlin/main.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package space.kscience.controls.demo.map
|
||||
|
||||
import androidx.compose.ui.window.application
|
||||
|
||||
|
||||
fun main() = application {
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
alias(spclibs.plugins.compose)
|
||||
alias(spclibs.plugins.compose.compiler)
|
||||
alias(spclibs.plugins.compose.jb)
|
||||
}
|
||||
|
||||
kotlin{
|
||||
|
@ -7,4 +7,4 @@ org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.jvmargs=-Xmx4096m
|
||||
|
||||
toolsVersion=0.15.2-kotlin-1.9.22
|
||||
toolsVersion=0.15.4-kotlin-2.0.0
|
@ -81,6 +81,8 @@ visionforge-markdown = { module = "space.kscience:visionforge-markdown", version
|
||||
visionforge-server = { module = "space.kscience:visionforge-server", version.ref = "visionforge" }
|
||||
visionforge-compose-html = { module = "space.kscience:visionforge-compose-html", version.ref = "visionforge" }
|
||||
|
||||
sciprog-maps-compose = "space.kscience:maps-kt-compose:0.3.0"
|
||||
|
||||
# Buildscript
|
||||
|
||||
[plugins]
|
||||
|
@ -86,5 +86,6 @@ include(
|
||||
":demo:motors",
|
||||
":demo:echo",
|
||||
":demo:mks-pdr900",
|
||||
":demo:constructor"
|
||||
":demo:constructor",
|
||||
":demo:devices-on-map"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user