Return notifications about pid and drive updates. Introduce debounce

This commit is contained in:
Alexander Nozik 2023-11-08 22:28:26 +03:00
parent fe98a836f8
commit 74301afb42
4 changed files with 74 additions and 43 deletions

View File

@ -74,6 +74,7 @@ public class VirtualDrive(
// compute new value based on velocity and acceleration from the previous step // compute new value based on velocity and acceleration from the previous step
positionState.value += velocity * dtSeconds + force / mass * dtSeconds.pow(2) / 2 positionState.value += velocity * dtSeconds + force / mass * dtSeconds.pow(2) / 2
propertyChanged(Drive.position, positionState.value)
// compute new velocity based on acceleration on the previous step // compute new velocity based on acceleration on the previous step
velocity += force / mass * dtSeconds velocity += force / mass * dtSeconds

View File

@ -62,6 +62,7 @@ public class PidRegulator(
lastPosition = drive.position lastPosition = drive.position
drive.force = pidParameters.kp * delta + pidParameters.ki * integral + pidParameters.kd * derivative drive.force = pidParameters.kp * delta + pidParameters.ki * integral + pidParameters.kd * derivative
propertyChanged(Regulator.position, drive.position)
} }
} }
} }

View File

@ -1,7 +1,11 @@
@file:OptIn(FlowPreview::class)
package space.kscience.controls.vision package space.kscience.controls.vision
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.transform
@ -25,6 +29,7 @@ import space.kscience.plotly.models.TraceValues
import space.kscience.plotly.scatter import space.kscience.plotly.scatter
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
private var TraceValues.values: List<Value> private var TraceValues.values: List<Value>
get() = value?.list ?: emptyList() get() = value?.list ?: emptyList()
@ -88,12 +93,13 @@ public fun Plot.plotDeviceProperty(
maxAge: Duration = 1.hours, maxAge: Duration = 1.hours,
maxPoints: Int = 800, maxPoints: Int = 800,
minPoints: Int = 400, minPoints: Int = 400,
debounceDuration: Duration = 10.milliseconds,
coroutineScope: CoroutineScope = device.context, coroutineScope: CoroutineScope = device.context,
configuration: Scatter.() -> Unit = {}, configuration: Scatter.() -> Unit = {},
): Job = scatter(configuration).run { ): Job = scatter(configuration).run {
val clock = device.context.clock val clock = device.context.clock
val data = TimeData() val data = TimeData()
device.propertyMessageFlow(propertyName).transform { device.propertyMessageFlow(propertyName).debounce(debounceDuration).transform {
data.append(it.time ?: clock.now(), it.value.extractValue()) data.append(it.time ?: clock.now(), it.value.extractValue())
data.trim(maxAge, maxPoints, minPoints) data.trim(maxAge, maxPoints, minPoints)
emit(data) emit(data)
@ -109,10 +115,11 @@ private fun <T> Trace.updateFromState(
maxAge: Duration = 1.hours, maxAge: Duration = 1.hours,
maxPoints: Int = 800, maxPoints: Int = 800,
minPoints: Int = 400, minPoints: Int = 400,
): Job{ debounceDuration: Duration = 10.milliseconds,
): Job {
val clock = context.clock val clock = context.clock
val data = TimeData() val data = TimeData()
return state.valueFlow.transform<T, TimeData> { return state.valueFlow.debounce(debounceDuration).transform<T, TimeData> {
data.append(clock.now(), it.extractValue()) data.append(clock.now(), it.extractValue())
data.trim(maxAge, maxPoints, minPoints) data.trim(maxAge, maxPoints, minPoints)
}.onEach { }.onEach {
@ -127,9 +134,10 @@ public fun <T> Plot.plotDeviceState(
maxAge: Duration = 1.hours, maxAge: Duration = 1.hours,
maxPoints: Int = 800, maxPoints: Int = 800,
minPoints: Int = 400, minPoints: Int = 400,
debounceDuration: Duration = 10.milliseconds,
configuration: Scatter.() -> Unit = {}, configuration: Scatter.() -> Unit = {},
): Job = scatter(configuration).run { ): Job = scatter(configuration).run {
updateFromState(context, state, extractValue, maxAge, maxPoints, minPoints) updateFromState(context, state, extractValue, maxAge, maxPoints, minPoints, debounceDuration)
} }
@ -139,9 +147,10 @@ public fun Plot.plotNumberState(
maxAge: Duration = 1.hours, maxAge: Duration = 1.hours,
maxPoints: Int = 800, maxPoints: Int = 800,
minPoints: Int = 400, minPoints: Int = 400,
debounceDuration: Duration = 10.milliseconds,
configuration: Scatter.() -> Unit = {}, configuration: Scatter.() -> Unit = {},
): Job = scatter(configuration).run { ): Job = scatter(configuration).run {
updateFromState(context, state, { asValue() }, maxAge, maxPoints, minPoints) updateFromState(context, state, { asValue() }, maxAge, maxPoints, minPoints, debounceDuration)
} }
@ -151,7 +160,8 @@ public fun Plot.plotBooleanState(
maxAge: Duration = 1.hours, maxAge: Duration = 1.hours,
maxPoints: Int = 800, maxPoints: Int = 800,
minPoints: Int = 400, minPoints: Int = 400,
debounceDuration: Duration = 10.milliseconds,
configuration: Bar.() -> Unit = {}, configuration: Bar.() -> Unit = {},
): Job = bar(configuration).run { ): Job = bar(configuration).run {
updateFromState(context, state, { asValue() }, maxAge, maxPoints, minPoints) updateFromState(context, state, { asValue() }, maxAge, maxPoints, minPoints, debounceDuration)
} }

View File

@ -3,17 +3,28 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": { "metadata": {},
"collapsed": true
},
"outputs": [], "outputs": [],
"source": [ "source": [
"USE(ControlsJupyter())" "//import space.kscience.controls.jupyter.ControlsJupyter\n",
"\n",
"//USE(ControlsJupyter())\n",
"USE{\n",
" repositories{\n",
" maven(\"https://repo.kotlin.link\")\n",
" }\n",
" dependencies{\n",
" implementation(\"space.kscience:controls-jupyter-jvm:0.3.0-dev-2\")\n",
" }\n",
"}"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"class LinearDrive(\n", "class LinearDrive(\n",
@ -34,14 +45,14 @@
" val position by property(state)\n", " val position by property(state)\n",
" var target by mutableProperty(pid.mutablePropertyAsState(Regulator.target, 0.0))\n", " var target by mutableProperty(pid.mutablePropertyAsState(Regulator.target, 0.0))\n",
"}\n" "}\n"
], ]
"metadata": {
"collapsed": false
}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"import kotlin.time.Duration.Companion.milliseconds\n", "import kotlin.time.Duration.Companion.milliseconds\n",
@ -57,14 +68,14 @@
")\n", ")\n",
"\n", "\n",
"val device = context.install(\"device\", LinearDrive(context, state, 0.005, pidParameters))" "val device = context.install(\"device\", LinearDrive(context, state, 0.005, pidParameters))"
], ]
"metadata": {
"collapsed": false
}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"\n", "\n",
@ -80,14 +91,14 @@
" sin(2 * PI * 21 * freq * t + 0.02 * (timeFromStart / pidParameters.timeStep))\n", " sin(2 * PI * 21 * freq * t + 0.02 * (timeFromStart / pidParameters.timeStep))\n",
" }\n", " }\n",
"}" "}"
], ]
"metadata": {
"collapsed": false
}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"val maxAge = 10.seconds\n", "val maxAge = 10.seconds\n",
@ -96,16 +107,18 @@
"VisionForge.fragment {\n", "VisionForge.fragment {\n",
" vision {\n", " vision {\n",
" plotly {\n", " plotly {\n",
" \n",
" plotDeviceProperty(device.pid, Regulator.target.name, maxAge = maxAge) {\n",
" name = \"target\"\n",
" }\n",
" \n",
" plotNumberState(context, state, maxAge = maxAge) {\n", " plotNumberState(context, state, maxAge = maxAge) {\n",
" name = \"real position\"\n", " name = \"real position\"\n",
" }\n", " }\n",
" \n",
" plotDeviceProperty(device.pid, Regulator.position.name, maxAge = maxAge) {\n", " plotDeviceProperty(device.pid, Regulator.position.name, maxAge = maxAge) {\n",
" name = \"read position\"\n", " name = \"read position\"\n",
" }\n", " }\n",
"\n",
" plotDeviceProperty(device.pid, Regulator.target.name, maxAge = maxAge) {\n",
" name = \"target\"\n",
" }\n",
" }\n", " }\n",
" }\n", " }\n",
"\n", "\n",
@ -122,23 +135,29 @@
" }\n", " }\n",
" }\n", " }\n",
"}" "}"
], ]
"metadata": {
"collapsed": false
}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"import kotlinx.coroutines.cancel\n", "import kotlinx.coroutines.cancel\n",
"\n", "\n",
"job.cancel()" "job.cancel()"
], ]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
} },
"outputs": [],
"source": []
}, },
{ {
"cell_type": "code", "cell_type": "code",
@ -156,21 +175,21 @@
"language": "kotlin", "language": "kotlin",
"name": "kotlin" "name": "kotlin"
}, },
"language_info": {
"name": "kotlin",
"version": "1.9.0",
"mimetype": "text/x-kotlin",
"file_extension": ".kt",
"pygments_lexer": "kotlin",
"codemirror_mode": "text/x-kotlin",
"nbconvert_exporter": ""
},
"ktnbPluginMetadata": { "ktnbPluginMetadata": {
"projectDependencies": [ "projectDependencies": [
"controls-kt.controls-jupyter.jvmMain" "controls-kt.controls-jupyter.jvmMain"
] ]
},
"language_info": {
"codemirror_mode": "text/x-kotlin",
"file_extension": ".kt",
"mimetype": "text/x-kotlin",
"name": "kotlin",
"nbconvert_exporter": "",
"pygments_lexer": "kotlin",
"version": "1.8.20"
} }
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 0 "nbformat_minor": 4
} }