Working prototype
This commit is contained in:
parent
75ee237ac6
commit
3982f02f64
@ -35,7 +35,7 @@ open class SimpleReadOnlyDeviceProperty(
|
|||||||
state.value = null
|
state.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun update(item: MetaItem<*>) {
|
protected fun update(item: MetaItem<*>) {
|
||||||
state.value = item
|
state.value = item
|
||||||
updateCallback(name, item)
|
updateCallback(name, item)
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ fun <D : DeviceBase> D.readingValue(
|
|||||||
this,
|
this,
|
||||||
default?.let { MetaItem.ValueItem(it) },
|
default?.let { MetaItem.ValueItem(it) },
|
||||||
PropertyDescriptor.invoke(descriptorBuilder),
|
PropertyDescriptor.invoke(descriptorBuilder),
|
||||||
{ MetaItem.ValueItem(Value.of(getter())) }
|
getter = { MetaItem.ValueItem(Value.of(getter())) }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.readingNumber(
|
fun <D : DeviceBase> D.readingNumber(
|
||||||
@ -113,7 +113,7 @@ fun <D : DeviceBase> D.readingNumber(
|
|||||||
this,
|
this,
|
||||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||||
PropertyDescriptor.invoke(descriptorBuilder),
|
PropertyDescriptor.invoke(descriptorBuilder),
|
||||||
{
|
getter = {
|
||||||
val number = getter()
|
val number = getter()
|
||||||
MetaItem.ValueItem(number.asValue())
|
MetaItem.ValueItem(number.asValue())
|
||||||
}
|
}
|
||||||
@ -127,7 +127,9 @@ fun <D : DeviceBase> D.readingMeta(
|
|||||||
this,
|
this,
|
||||||
default?.let { MetaItem.NodeItem(it) },
|
default?.let { MetaItem.NodeItem(it) },
|
||||||
PropertyDescriptor.invoke(descriptorBuilder),
|
PropertyDescriptor.invoke(descriptorBuilder),
|
||||||
{ MetaItem.NodeItem(MetaBuilder().apply { getter() }) }
|
getter = {
|
||||||
|
MetaItem.NodeItem(MetaBuilder().apply { getter() })
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@ -138,7 +140,7 @@ class SimpleDeviceProperty(
|
|||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
updateCallback: (name: String, item: MetaItem<*>?) -> Unit,
|
updateCallback: (name: String, item: MetaItem<*>?) -> Unit,
|
||||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||||
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> Unit
|
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||||
) : SimpleReadOnlyDeviceProperty(name, default, descriptor, scope, updateCallback, getter), DeviceProperty {
|
) : SimpleReadOnlyDeviceProperty(name, default, descriptor, scope, updateCallback, getter), DeviceProperty {
|
||||||
|
|
||||||
override var value: MetaItem<*>?
|
override var value: MetaItem<*>?
|
||||||
@ -163,7 +165,9 @@ class SimpleDeviceProperty(
|
|||||||
//all device operations should be run on device context
|
//all device operations should be run on device context
|
||||||
withContext(scope.coroutineContext) {
|
withContext(scope.coroutineContext) {
|
||||||
//TODO add error catching
|
//TODO add error catching
|
||||||
setter(oldValue, item)
|
setter(oldValue, item)?.let {
|
||||||
|
update(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,7 +178,7 @@ private class DevicePropertyDelegate<D : DeviceBase>(
|
|||||||
val default: MetaItem<*>?,
|
val default: MetaItem<*>?,
|
||||||
val descriptor: PropertyDescriptor = PropertyDescriptor.empty(),
|
val descriptor: PropertyDescriptor = PropertyDescriptor.empty(),
|
||||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||||
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> Unit
|
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||||
) : ReadOnlyProperty<D, SimpleDeviceProperty> {
|
) : ReadOnlyProperty<D, SimpleDeviceProperty> {
|
||||||
|
|
||||||
override fun getValue(thisRef: D, property: KProperty<*>): SimpleDeviceProperty {
|
override fun getValue(thisRef: D, property: KProperty<*>): SimpleDeviceProperty {
|
||||||
@ -198,7 +202,7 @@ fun <D : DeviceBase> D.writing(
|
|||||||
default: MetaItem<*>? = null,
|
default: MetaItem<*>? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> Unit
|
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||||
): ReadOnlyProperty<D, SimpleDeviceProperty> = DevicePropertyDelegate(
|
): ReadOnlyProperty<D, SimpleDeviceProperty> = DevicePropertyDelegate(
|
||||||
this,
|
this,
|
||||||
default,
|
default,
|
||||||
@ -214,7 +218,7 @@ fun <D : DeviceBase> D.writingVirtual(
|
|||||||
default,
|
default,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = { it ?: default },
|
getter = { it ?: default },
|
||||||
setter = { _, _ -> }
|
setter = { _, newItem -> newItem }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.writingVirtual(
|
fun <D : DeviceBase> D.writingVirtual(
|
||||||
@ -224,20 +228,20 @@ fun <D : DeviceBase> D.writingVirtual(
|
|||||||
MetaItem.ValueItem(default),
|
MetaItem.ValueItem(default),
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
getter = { it ?: MetaItem.ValueItem(default) },
|
getter = { it ?: MetaItem.ValueItem(default) },
|
||||||
setter = { _, _ -> }
|
setter = { _, newItem -> newItem }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <D : DeviceBase> D.writingDouble(
|
fun <D : DeviceBase> D.writingDouble(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
getter: suspend (Double) -> Double,
|
getter: suspend (Double) -> Double,
|
||||||
setter: suspend (oldValue: Double?, newValue: Double) -> Unit
|
setter: suspend (oldValue: Double?, newValue: Double) -> Double?
|
||||||
): ReadOnlyProperty<D, SimpleDeviceProperty> {
|
): ReadOnlyProperty<D, SimpleDeviceProperty> {
|
||||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||||
MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue())
|
MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> Unit = { oldValue, newValue ->
|
val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue ->
|
||||||
setter(oldValue.double, newValue.double ?: Double.NaN)
|
setter(oldValue.double, newValue.double ?: Double.NaN)?.asMetaItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
return DevicePropertyDelegate(
|
return DevicePropertyDelegate(
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package hep.dataforge.control.base
|
||||||
|
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.values.asValue
|
||||||
|
|
||||||
|
fun Double.asMetaItem(): MetaItem.ValueItem = MetaItem.ValueItem(asValue())
|
@ -12,7 +12,6 @@ import hep.dataforge.meta.string
|
|||||||
import hep.dataforge.meta.wrap
|
import hep.dataforge.meta.wrap
|
||||||
import kotlinx.io.Binary
|
import kotlinx.io.Binary
|
||||||
|
|
||||||
|
|
||||||
interface MessageConsumer {
|
interface MessageConsumer {
|
||||||
fun consume(message: Envelope): Unit
|
fun consume(message: Envelope): Unit
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
suspend fun Device.valueFlow(): Flow<Pair<String, MetaItem<*>>> = callbackFlow {
|
suspend fun Device.flowValues(): Flow<Pair<String, MetaItem<*>>> = callbackFlow {
|
||||||
val listener = object : PropertyChangeListener {
|
val listener = object : PropertyChangeListener {
|
||||||
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
|
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
|
@ -2,9 +2,13 @@ package hep.dataforge.control.controlers
|
|||||||
|
|
||||||
import hep.dataforge.control.base.DeviceProperty
|
import hep.dataforge.control.base.DeviceProperty
|
||||||
import hep.dataforge.control.base.ReadOnlyDeviceProperty
|
import hep.dataforge.control.base.ReadOnlyDeviceProperty
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.meta.double
|
import hep.dataforge.meta.double
|
||||||
import hep.dataforge.meta.map
|
import hep.dataforge.meta.map
|
||||||
import hep.dataforge.meta.transform
|
import hep.dataforge.values.asValue
|
||||||
|
|
||||||
fun ReadOnlyDeviceProperty.double() = map { it.double }
|
fun ReadOnlyDeviceProperty.double() = map { it.double }
|
||||||
fun DeviceProperty.double() = transform { it.double ?: Double.NaN }
|
fun DeviceProperty.double() = map(
|
||||||
|
reader = { it.double ?: Double.NaN },
|
||||||
|
writer = { MetaItem.ValueItem(it.asValue()) }
|
||||||
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.3.72"
|
kotlin("jvm") version "1.3.72"
|
||||||
|
id("org.openjfx.javafxplugin") version "0.0.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
val plotlyVersion: String by rootProject.extra
|
val plotlyVersion: String by rootProject.extra
|
||||||
@ -14,11 +15,17 @@ repositories{
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies{
|
dependencies{
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
|
||||||
implementation(project(":dataforge-control-core"))
|
implementation(project(":dataforge-control-core"))
|
||||||
|
implementation("no.tornado:tornadofx:1.7.20")
|
||||||
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
implementation("scientifik:plotlykt-server:$plotlyVersion")
|
implementation("scientifik:plotlykt-server:$plotlyVersion")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
kotlinOptions.jvmTarget = "11"
|
kotlinOptions.jvmTarget = "11"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
javafx{
|
||||||
|
version = "14"
|
||||||
|
modules("javafx.controls")
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
package hep.dataforge.control.demo
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
val producerDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
runBlocking {
|
|
||||||
val test = MutableStateFlow(0)
|
|
||||||
|
|
||||||
launch {
|
|
||||||
var counter = 0
|
|
||||||
while (isActive){
|
|
||||||
delay(500)
|
|
||||||
counter++
|
|
||||||
println("produced $counter")
|
|
||||||
test.value = counter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
launch(producerDispatcher) {
|
|
||||||
test.collect{
|
|
||||||
println("collected $it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,117 @@
|
|||||||
|
package hep.dataforge.control.demo
|
||||||
|
|
||||||
|
import javafx.scene.Parent
|
||||||
|
import javafx.scene.control.Slider
|
||||||
|
import javafx.scene.layout.Priority
|
||||||
|
import javafx.stage.Stage
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import scientifik.plotly.server.PlotlyServer
|
||||||
|
import tornadofx.*
|
||||||
|
import java.awt.Desktop
|
||||||
|
import java.net.URI
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
val logger = LoggerFactory.getLogger("Demo")
|
||||||
|
|
||||||
|
class DemoController : Controller(), CoroutineScope {
|
||||||
|
|
||||||
|
var device: DemoDevice? = null
|
||||||
|
var server: PlotlyServer? = null
|
||||||
|
override val coroutineContext: CoroutineContext = GlobalScope.newCoroutineContext(Dispatchers.Default) + Job()
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
launch {
|
||||||
|
device = DemoDevice(this)
|
||||||
|
server = device?.let { servePlots(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shutdown() {
|
||||||
|
logger.info("Shutting down...")
|
||||||
|
server?.stop()
|
||||||
|
logger.info("Visualization server stopped")
|
||||||
|
device?.close()
|
||||||
|
logger.info("Device server stopped")
|
||||||
|
cancel("Application context closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DemoControllerView : View(title = " Demo controller remote") {
|
||||||
|
private val controller: DemoController by inject()
|
||||||
|
private var timeScaleSlider: Slider by singleAssign()
|
||||||
|
private var xScaleSlider: Slider by singleAssign()
|
||||||
|
private var yScaleSlider: Slider by singleAssign()
|
||||||
|
|
||||||
|
override val root: Parent = vbox {
|
||||||
|
hbox {
|
||||||
|
label("Time scale")
|
||||||
|
pane {
|
||||||
|
hgrow = Priority.ALWAYS
|
||||||
|
}
|
||||||
|
timeScaleSlider = slider(1000..10000, 5000) {
|
||||||
|
isShowTickLabels = true
|
||||||
|
isShowTickMarks = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hbox {
|
||||||
|
label("X scale")
|
||||||
|
pane {
|
||||||
|
hgrow = Priority.ALWAYS
|
||||||
|
}
|
||||||
|
xScaleSlider = slider(0.0..2.0, 1.0) {
|
||||||
|
isShowTickLabels = true
|
||||||
|
isShowTickMarks = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hbox {
|
||||||
|
label("Y scale")
|
||||||
|
pane {
|
||||||
|
hgrow = Priority.ALWAYS
|
||||||
|
}
|
||||||
|
yScaleSlider = slider(0.0..2.0, 1.0) {
|
||||||
|
isShowTickLabels = true
|
||||||
|
isShowTickMarks = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button("Submit") {
|
||||||
|
useMaxWidth = true
|
||||||
|
action {
|
||||||
|
controller.device?.apply {
|
||||||
|
timeScaleValue = timeScaleSlider.value
|
||||||
|
sinScaleValue = xScaleSlider.value
|
||||||
|
cosScaleValue = yScaleSlider.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button("Show plots") {
|
||||||
|
useMaxWidth = true
|
||||||
|
action {
|
||||||
|
controller.server?.run {
|
||||||
|
val uri = URI("http", null, host, port, null, null, null)
|
||||||
|
Desktop.getDesktop().browse(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DemoControllerApp : App(DemoControllerView::class) {
|
||||||
|
private val controller: DemoController by inject()
|
||||||
|
|
||||||
|
override fun start(stage: Stage) {
|
||||||
|
super.start(stage)
|
||||||
|
controller.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
controller.shutdown()
|
||||||
|
super.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
launch<DemoControllerApp>()
|
||||||
|
}
|
@ -22,34 +22,38 @@ class DemoDevice(parentScope: CoroutineScope = GlobalScope) : DeviceBase() {
|
|||||||
parentScope.coroutineContext + executor.asCoroutineDispatcher()
|
parentScope.coroutineContext + executor.asCoroutineDispatcher()
|
||||||
)
|
)
|
||||||
|
|
||||||
val scaleProperty: SimpleDeviceProperty by writingVirtual(5000.0.asValue())
|
val timeScale: SimpleDeviceProperty by writingVirtual(5000.0.asValue())
|
||||||
var scale by scaleProperty.double()
|
var timeScaleValue by timeScale.double()
|
||||||
|
|
||||||
val resetScale: Action by action {
|
val resetScale: Action by action {
|
||||||
scale = 5000.0
|
timeScaleValue = 5000.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sinScale by writingVirtual(1.0.asValue())
|
||||||
|
var sinScaleValue by sinScale.double()
|
||||||
val sin by readingNumber {
|
val sin by readingNumber {
|
||||||
val time = Instant.now()
|
val time = Instant.now()
|
||||||
sin(time.toEpochMilli().toDouble() / scale)
|
sin(time.toEpochMilli().toDouble() / timeScaleValue)*sinScaleValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val cosScale by writingVirtual(1.0.asValue())
|
||||||
|
var cosScaleValue by cosScale.double()
|
||||||
val cos by readingNumber {
|
val cos by readingNumber {
|
||||||
val time = Instant.now()
|
val time = Instant.now()
|
||||||
cos(time.toEpochMilli().toDouble() / scale)
|
cos(time.toEpochMilli().toDouble() / timeScaleValue)*cosScaleValue
|
||||||
}
|
}
|
||||||
|
|
||||||
val coordinates by readingMeta {
|
val coordinates by readingMeta {
|
||||||
val time = Instant.now()
|
val time = Instant.now()
|
||||||
"time" put time.toEpochMilli()
|
"time" put time.toEpochMilli()
|
||||||
"x" put sin(time.toEpochMilli().toDouble() / scale)
|
"x" put sin(time.toEpochMilli().toDouble() / timeScaleValue)*sinScaleValue
|
||||||
"y" put cos(time.toEpochMilli().toDouble() / scale)
|
"y" put cos(time.toEpochMilli().toDouble() / timeScaleValue)*cosScaleValue
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
sin.readEvery(0.2.seconds)
|
sin.readEvery(0.2.seconds)
|
||||||
cos.readEvery(0.2.seconds)
|
cos.readEvery(0.2.seconds)
|
||||||
coordinates.readEvery(0.2.seconds)
|
coordinates.readEvery(0.3.seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
package hep.dataforge.control.demo
|
|
||||||
|
|
||||||
import hep.dataforge.meta.double
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
|
||||||
import kotlinx.coroutines.flow.zip
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import scientifik.plotly.Plotly
|
|
||||||
import scientifik.plotly.layout
|
|
||||||
import scientifik.plotly.models.Trace
|
|
||||||
import scientifik.plotly.server.pushUpdates
|
|
||||||
import scientifik.plotly.server.serve
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
runBlocking(Dispatchers.Default) {
|
|
||||||
val device = DemoDevice()
|
|
||||||
|
|
||||||
val sinFlow = device.sin.flow()
|
|
||||||
val cosFlow = device.cos.flow()
|
|
||||||
val sinCosFlow = sinFlow.zip(cosFlow) { sin, cos ->
|
|
||||||
sin.double to cos.double
|
|
||||||
}
|
|
||||||
|
|
||||||
// launch {
|
|
||||||
// device.valueFlow().collect { (name, item) ->
|
|
||||||
// if (name == "sin") {
|
|
||||||
// println("Device produced $item")
|
|
||||||
// println("Sin value is ${sinFlow.value}")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// launch {
|
|
||||||
// sinFlow.mapNotNull { it.double }.collect {
|
|
||||||
// println("Device processed $it")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
val server = Plotly.serve(this) {
|
|
||||||
plot(rowNumber = 0, colOrderNumber = 0, size = 6) {
|
|
||||||
layout {
|
|
||||||
title = "sin property"
|
|
||||||
xaxis.title = "point index"
|
|
||||||
yaxis.title = "sin"
|
|
||||||
}
|
|
||||||
val trace = Trace.empty()
|
|
||||||
data.add(trace)
|
|
||||||
launch {
|
|
||||||
val queue = ConcurrentLinkedQueue<Double>()
|
|
||||||
|
|
||||||
sinFlow.mapNotNull { it.double }.collect {
|
|
||||||
queue.add(it)
|
|
||||||
if (queue.size >= 100) {
|
|
||||||
queue.poll()
|
|
||||||
}
|
|
||||||
trace.y.numbers = queue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
plot(rowNumber = 0, colOrderNumber = 1, size = 6) {
|
|
||||||
layout {
|
|
||||||
title = "cos property"
|
|
||||||
xaxis.title = "point index"
|
|
||||||
yaxis.title = "cos"
|
|
||||||
}
|
|
||||||
val trace = Trace.empty()
|
|
||||||
data.add(trace)
|
|
||||||
launch {
|
|
||||||
val queue = ConcurrentLinkedQueue<Double>()
|
|
||||||
|
|
||||||
cosFlow.mapNotNull { it.double }.collect {
|
|
||||||
queue.add(it)
|
|
||||||
if (queue.size >= 100) {
|
|
||||||
queue.poll()
|
|
||||||
}
|
|
||||||
trace.y.numbers = queue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
plot(rowNumber = 1, colOrderNumber = 0, size = 12) {
|
|
||||||
layout {
|
|
||||||
title = "cos vs sin"
|
|
||||||
xaxis.title = "sin"
|
|
||||||
yaxis.title = "cos"
|
|
||||||
}
|
|
||||||
val trace = Trace.empty()
|
|
||||||
data.add(trace)
|
|
||||||
launch {
|
|
||||||
val queue = ConcurrentLinkedQueue<Pair<Double, Double>>()
|
|
||||||
|
|
||||||
sinCosFlow.collect { pair ->
|
|
||||||
val x = pair.first ?: return@collect
|
|
||||||
val y = pair.second ?: return@collect
|
|
||||||
queue.add(x to y)
|
|
||||||
if (queue.size >= 20) {
|
|
||||||
queue.poll()
|
|
||||||
}
|
|
||||||
trace.x.numbers = queue.map { it.first }
|
|
||||||
trace.y.numbers = queue.map { it.second }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}.pushUpdates()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
readLine()
|
|
||||||
|
|
||||||
println("Stopping")
|
|
||||||
server.stop()
|
|
||||||
device.close()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package hep.dataforge.control.demo
|
|
||||||
|
|
||||||
import hep.dataforge.meta.MetaItem
|
|
||||||
import hep.dataforge.meta.double
|
|
||||||
import hep.dataforge.values.Null
|
|
||||||
import hep.dataforge.values.asValue
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
runBlocking {
|
|
||||||
val flow: MutableStateFlow<MetaItem<*>> = MutableStateFlow<MetaItem<*>>(MetaItem.ValueItem(Null))
|
|
||||||
|
|
||||||
val collector = launch {
|
|
||||||
flow.map { it.double }.collect {
|
|
||||||
println(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repeat(10) {
|
|
||||||
delay(10)
|
|
||||||
flow.value = MetaItem.ValueItem(it.toDouble().asValue())
|
|
||||||
}
|
|
||||||
collector.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,94 @@
|
|||||||
|
package hep.dataforge.control.demo
|
||||||
|
|
||||||
|
import hep.dataforge.meta.double
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import scientifik.plotly.Plotly
|
||||||
|
import scientifik.plotly.layout
|
||||||
|
import scientifik.plotly.models.Trace
|
||||||
|
import scientifik.plotly.server.PlotlyServer
|
||||||
|
import scientifik.plotly.server.pushUpdates
|
||||||
|
import scientifik.plotly.server.serve
|
||||||
|
import scientifik.plotly.trace
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-place replacement for absent method from stdlib
|
||||||
|
*/
|
||||||
|
fun <T> Flow<T>.windowed(size: Int): Flow<Iterable<T>> {
|
||||||
|
val queue = ConcurrentLinkedQueue<T>()
|
||||||
|
return flow {
|
||||||
|
this@windowed.collect {
|
||||||
|
queue.add(it)
|
||||||
|
if (queue.size >= size) {
|
||||||
|
queue.poll()
|
||||||
|
}
|
||||||
|
emit(queue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Trace.updateFrom(axisName: String, flow: Flow<Iterable<Double>>) {
|
||||||
|
flow.collect {
|
||||||
|
axis(axisName).numbers = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Trace.updateXYFrom(flow: Flow<Iterable<Pair<Double, Double>>>) {
|
||||||
|
flow.collect { pairs ->
|
||||||
|
x.numbers = pairs.map { it.first }
|
||||||
|
y.numbers = pairs.map { it.second }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CoroutineScope.servePlots(device: DemoDevice): PlotlyServer {
|
||||||
|
val sinFlow = device.sin.flow()
|
||||||
|
val cosFlow = device.cos.flow()
|
||||||
|
val sinCosFlow = sinFlow.zip(cosFlow) { sin, cos ->
|
||||||
|
sin.double!! to cos.double!!
|
||||||
|
}
|
||||||
|
|
||||||
|
return Plotly.serve(this) {
|
||||||
|
plot(rowNumber = 0, colOrderNumber = 0, size = 6) {
|
||||||
|
layout {
|
||||||
|
title = "sin property"
|
||||||
|
xaxis.title = "point index"
|
||||||
|
yaxis.title = "sin"
|
||||||
|
}
|
||||||
|
trace {
|
||||||
|
this@servePlots.launch {
|
||||||
|
val flow: Flow<Iterable<Double>> = sinFlow.mapNotNull { it.double }.windowed(100)
|
||||||
|
updateFrom(Trace.Y_AXIS, flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plot(rowNumber = 0, colOrderNumber = 1, size = 6) {
|
||||||
|
layout {
|
||||||
|
title = "cos property"
|
||||||
|
xaxis.title = "point index"
|
||||||
|
yaxis.title = "cos"
|
||||||
|
}
|
||||||
|
trace {
|
||||||
|
this@servePlots.launch {
|
||||||
|
val flow: Flow<Iterable<Double>> = cosFlow.mapNotNull { it.double }.windowed(100)
|
||||||
|
updateFrom(Trace.Y_AXIS, flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plot(rowNumber = 1, colOrderNumber = 0, size = 12) {
|
||||||
|
layout {
|
||||||
|
title = "cos vs sin"
|
||||||
|
xaxis.title = "sin"
|
||||||
|
yaxis.title = "cos"
|
||||||
|
}
|
||||||
|
trace {
|
||||||
|
name = "non-synchronized"
|
||||||
|
this@servePlots.launch {
|
||||||
|
val flow: Flow<Iterable<Pair<Double, Double>>> = sinCosFlow.windowed(30)
|
||||||
|
updateXYFrom(flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.pushUpdates()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user