Fix PID demo

This commit is contained in:
Alexander Nozik 2024-06-05 17:19:20 +03:00
parent 91f860adf6
commit c63c2db651
20 changed files with 121 additions and 56 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
# Created by .ignore support plugin (hsz.mobi)
.idea/
.gradle
.kotlin
*.iws
*.iml

View File

@ -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

View File

@ -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 )
}
}

View File

@ -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 {

View File

@ -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,

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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`
}

View File

@ -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")
}
}
}

View File

@ -1,6 +1,7 @@
plugins {
kotlin("jvm")
alias(spclibs.plugins.compose)
alias(spclibs.plugins.compose.compiler)
alias(spclibs.plugins.compose.jb)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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")
}

View File

@ -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)
}

View 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"
}
}

View File

@ -0,0 +1,8 @@
package space.kscience.controls.demo.map
import androidx.compose.ui.window.application
fun main() = application {
}

View File

@ -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{

View File

@ -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

View File

@ -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]

View File

@ -86,5 +86,6 @@ include(
":demo:motors",
":demo:echo",
":demo:mks-pdr900",
":demo:constructor"
":demo:constructor",
":demo:devices-on-map"
)