Merge remote-tracking branch 'origin/master'

# Conflicts:
#	dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt
#	dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/MessageController.kt
#	dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/MessageFlow.kt
This commit is contained in:
Alexander Nozik 2020-06-30 22:06:39 +03:00
commit a3536c7a17
13 changed files with 95 additions and 20 deletions

View File

@ -1,4 +1,56 @@
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
# DataForge-control # DataForge-control
Data acquisition framework based on DataForge
DataForge-control is a data acquisition framework (work in progress). It is
based on DataForge, a software framework for automated data processing.
This repository contains a prototype of API and simple implementation
of a slow control system, including a demo.
DataForge-control uses some concepts and modules of DataForge,
such as `Meta` (immutable tree-like structure) and `MetaItem` (which
includes a scalar value, or a tree of values, easily convertable to/from JSON
if needed).
To learn more about DataForge, please consult the following URLs:
* [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core)
* [DataForge documentation](http://npm.mipt.ru/dataforge/)
* [Original implementation of DataForge](https://bitbucket.org/Altavir/dataforge/src/default/)
DataForge-control is a [Kotlin-multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html)
application. Asynchronous operations are implemented with
[kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) library.
### Features
Among other things, you can:
- Describe devices and their properties.
- Collect data from devices and execute arbitrary actions supported by a device.
- Property values can be cached in the system and requested from devices as needed, asynchronously.
- Connect devices to event bus via bidirectional message flows.
### `dataforge-control-core` module packages
- `api` - defines API for device management. The main class here is
[`Device`](dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt).
Generally, a Device has Properties that can be read and written. Also, some Actions
can optionally be applied on a device (may or may not affect properties).
- `base` - contains baseline `Device` implementation
[`DeviceBase`](dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt)
and property implementation, including property asynchronous flows.
- `controllers` - implements Message Controller that can be attached to the event bus, Message
and Property flows.
### `demo` module
The demo includes a simple mock device with a few properties changing as `sin` and `cos` of
the current time. The device is configurable via a simple TornadoFX-based control panel.
You can run a demo by executing `application/run` Gradle task.
The graphs are displayed using [plotly.kt](https://github.com/mipt-npm/plotly.kt) library.
Example view of a demo:
![](docs/pictures/demo-view.png)

View File

@ -6,6 +6,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.io.Closeable import kotlinx.io.Closeable
/**
* General interface describing a managed Device
*/
interface Device: Closeable { interface Device: Closeable {
/** /**
* List of supported property descriptors * List of supported property descriptors
@ -13,7 +16,8 @@ interface Device: Closeable {
val propertyDescriptors: Collection<PropertyDescriptor> val propertyDescriptors: Collection<PropertyDescriptor>
/** /**
* List of supported requests descriptors * List of supported action descriptors. Action is a request to the device that
* may or may not change the properties
*/ */
val actionDescriptors: Collection<ActionDescriptor> val actionDescriptors: Collection<ActionDescriptor>
@ -30,12 +34,13 @@ interface Device: Closeable {
fun registerListener(listener: DeviceListener, owner: Any? = listener) fun registerListener(listener: DeviceListener, owner: Any? = listener)
/** /**
* Remove all listeners belonging to specified owner * Remove all listeners belonging to the specified owner
*/ */
fun removeListener(owner: Any?) fun removeListeners(owner: Any?)
/** /**
* Get the value of the property or throw error if property in not defined. Suspend if property value is not available * Get the value of the property or throw error if property in not defined.
* Suspend if property value is not available
*/ */
suspend fun getProperty(propertyName: String): MetaItem<*> suspend fun getProperty(propertyName: String): MetaItem<*>
@ -51,8 +56,8 @@ interface Device: Closeable {
suspend fun setProperty(propertyName: String, value: MetaItem<*>) suspend fun setProperty(propertyName: String, value: MetaItem<*>)
/** /**
* Send a request and suspend caller while request is being processed. * Send an action request and suspend caller while request is being processed.
* Could return null if request does not return meaningful answer. * Could return null if request does not return a meaningful answer.
*/ */
suspend fun call(action: String, argument: MetaItem<*>? = null): MetaItem<*>? suspend fun call(action: String, argument: MetaItem<*>? = null): MetaItem<*>?

View File

@ -18,6 +18,6 @@ suspend fun DeviceHub.setProperty(deviceName: String, propertyName: String, valu
.setProperty(propertyName, value) .setProperty(propertyName, value)
} }
suspend fun DeviceHub.request(deviceName: String, command: String, argument: MetaItem<*>?): MetaItem<*>? = suspend fun DeviceHub.call(deviceName: String, command: String, argument: MetaItem<*>?): MetaItem<*>? =
(getDevice(deviceName) ?: error("Device with name $deviceName not found in the hub")) (getDevice(deviceName) ?: error("Device with name $deviceName not found in the hub"))
.call(command, argument) .call(command, argument)

View File

@ -2,6 +2,11 @@ package hep.dataforge.control.api
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.MetaItem
/**
* PropertyChangeListener Interface
* [value] is a new value that property has after a change; null is for invalid state.
*/
interface PropertyChangeListener {
interface DeviceListener { interface DeviceListener {
fun propertyChanged(propertyName: String, value: MetaItem<*>?) fun propertyChanged(propertyName: String, value: MetaItem<*>?)
//TODO add general message listener method //TODO add general message listener method

View File

@ -6,6 +6,9 @@ import hep.dataforge.control.api.DeviceListener
import hep.dataforge.control.api.PropertyDescriptor import hep.dataforge.control.api.PropertyDescriptor
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.MetaItem
/**
* Baseline implementation of [Device] interface
*/
abstract class DeviceBase : Device { abstract class DeviceBase : Device {
private val properties = HashMap<String, ReadOnlyDeviceProperty>() private val properties = HashMap<String, ReadOnlyDeviceProperty>()
private val actions = HashMap<String, Action>() private val actions = HashMap<String, Action>()
@ -16,7 +19,7 @@ abstract class DeviceBase : Device {
listeners.add(owner to listener) listeners.add(owner to listener)
} }
override fun removeListener(owner: Any?) { override fun removeListeners(owner: Any?) {
listeners.removeAll { it.first == owner } listeners.removeAll { it.first == owner }
} }

View File

@ -1,4 +1,4 @@
package hep.dataforge.control.controlers package hep.dataforge.control.controllers
import hep.dataforge.control.controlers.DeviceMessage.Companion.PAYLOAD_VALUE_KEY import hep.dataforge.control.controlers.DeviceMessage.Companion.PAYLOAD_VALUE_KEY
import hep.dataforge.meta.* import hep.dataforge.meta.*

View File

@ -1,8 +1,10 @@
package hep.dataforge.control.controlers package hep.dataforge.control.controllers
import hep.dataforge.control.api.Device import hep.dataforge.control.api.Device
import hep.dataforge.control.api.DeviceListener import hep.dataforge.control.api.DeviceListener
import hep.dataforge.control.controlers.DeviceMessage.Companion.PROPERTY_CHANGED_ACTION import hep.dataforge.control.controlers.DeviceMessage.Companion.PROPERTY_CHANGED_ACTION
import hep.dataforge.control.api.PropertyChangeListener
import hep.dataforge.control.controllers.DevicePropertyMessage.Companion.PROPERTY_CHANGED_ACTION
import hep.dataforge.io.Envelope import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder import hep.dataforge.io.Responder
import hep.dataforge.io.SimpleEnvelope import hep.dataforge.io.SimpleEnvelope

View File

@ -1,4 +1,4 @@
package hep.dataforge.control.controlers package hep.dataforge.control.controllers
import hep.dataforge.control.api.Device import hep.dataforge.control.api.Device
import hep.dataforge.control.api.DeviceListener import hep.dataforge.control.api.DeviceListener
@ -21,8 +21,8 @@ suspend fun Device.flowValues(): Flow<Pair<String, MetaItem<*>>> = callbackFlow
} }
} }
} }
registerListener(listener, listener) registerListener(listener)
awaitClose { awaitClose {
removeListener(listener) removeListeners(listener)
} }
} }

View File

@ -1,4 +1,4 @@
package hep.dataforge.control.controlers package hep.dataforge.control.controllers
import hep.dataforge.control.base.DeviceProperty import hep.dataforge.control.base.DeviceProperty
import hep.dataforge.control.base.ReadOnlyDeviceProperty import hep.dataforge.control.base.ReadOnlyDeviceProperty

View File

@ -1,6 +1,7 @@
plugins { plugins {
kotlin("jvm") version "1.3.72" kotlin("jvm") version "1.3.72"
id("org.openjfx.javafxplugin") version "0.0.8" id("org.openjfx.javafxplugin") version "0.0.8"
`application`
} }
val plotlyVersion: String by rootProject.extra val plotlyVersion: String by rootProject.extra
@ -31,4 +32,8 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach
javafx{ javafx{
version = "14" version = "14"
modules("javafx.controls") modules("javafx.controls")
}
application{
mainClassName = "hep.dataforge.control.demo.DemoControllerViewKt"
} }

View File

@ -1,7 +1,7 @@
package hep.dataforge.control.demo package hep.dataforge.control.demo
import hep.dataforge.control.base.* import hep.dataforge.control.base.*
import hep.dataforge.control.controlers.double import hep.dataforge.control.controllers.double
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -26,10 +26,6 @@ class DemoDevice(parentScope: CoroutineScope = GlobalScope) : DeviceBase() {
val timeScale: IsolatedDeviceProperty by writingVirtual(5000.0.asValue()) val timeScale: IsolatedDeviceProperty by writingVirtual(5000.0.asValue())
var timeScaleValue by timeScale.double() var timeScaleValue by timeScale.double()
val resetScale: Action by action {
timeScaleValue = 5000.0
}
val sinScale by writingVirtual(1.0.asValue()) val sinScale by writingVirtual(1.0.asValue())
var sinScaleValue by sinScale.double() var sinScaleValue by sinScale.double()
val sin by readingNumber { val sin by readingNumber {
@ -51,6 +47,13 @@ class DemoDevice(parentScope: CoroutineScope = GlobalScope) : DeviceBase() {
"y" put cos(time.toEpochMilli().toDouble() / timeScaleValue)*cosScaleValue "y" put cos(time.toEpochMilli().toDouble() / timeScaleValue)*cosScaleValue
} }
val resetScale: Action by action {
timeScaleValue = 5000.0
sinScaleValue = 1.0
cosScaleValue = 1.0
}
init { init {
sin.readEvery(0.2.seconds) sin.readEvery(0.2.seconds)
cos.readEvery(0.2.seconds) cos.readEvery(0.2.seconds)

BIN
docs/pictures/demo-view.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB