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:
commit
a3536c7a17
54
README.md
54
README.md
@ -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)
|
||||
|
@ -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<*>?
|
||||
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
|
@ -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.*
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
||||
@ -31,4 +32,8 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach
|
||||
javafx{
|
||||
version = "14"
|
||||
modules("javafx.controls")
|
||||
}
|
||||
|
||||
application{
|
||||
mainClassName = "hep.dataforge.control.demo.DemoControllerViewKt"
|
||||
}
|
@ -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
BIN
docs/pictures/demo-view.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
Loading…
Reference in New Issue
Block a user