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)
# 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.io.Closeable
/**
* General interface describing a managed Device
*/
interface Device: Closeable {
/**
* List of supported property descriptors
@ -13,7 +16,8 @@ interface Device: Closeable {
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>
@ -30,12 +34,13 @@ interface Device: Closeable {
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<*>
@ -51,8 +56,8 @@ interface Device: Closeable {
suspend fun setProperty(propertyName: String, value: MetaItem<*>)
/**
* Send a request and suspend caller while request is being processed.
* Could return null if request does not return meaningful answer.
* Send an action request and suspend caller while request is being processed.
* Could return null if request does not return a meaningful answer.
*/
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)
}
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"))
.call(command, argument)

View File

@ -2,6 +2,11 @@ package hep.dataforge.control.api
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 {
fun propertyChanged(propertyName: String, value: MetaItem<*>?)
//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.meta.MetaItem
/**
* Baseline implementation of [Device] interface
*/
abstract class DeviceBase : Device {
private val properties = HashMap<String, ReadOnlyDeviceProperty>()
private val actions = HashMap<String, Action>()
@ -16,7 +19,7 @@ abstract class DeviceBase : Device {
listeners.add(owner to listener)
}
override fun removeListener(owner: Any?) {
override fun removeListeners(owner: Any?) {
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.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.DeviceListener
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.Responder
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.DeviceListener
@ -21,8 +21,8 @@ suspend fun Device.flowValues(): Flow<Pair<String, MetaItem<*>>> = callbackFlow
}
}
}
registerListener(listener, listener)
registerListener(listener)
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.ReadOnlyDeviceProperty

View File

@ -1,6 +1,7 @@
plugins {
kotlin("jvm") version "1.3.72"
id("org.openjfx.javafxplugin") version "0.0.8"
`application`
}
val plotlyVersion: String by rootProject.extra
@ -32,3 +33,7 @@ javafx{
version = "14"
modules("javafx.controls")
}
application{
mainClassName = "hep.dataforge.control.demo.DemoControllerViewKt"
}

View File

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