diff --git a/README.md b/README.md index 9dbfe11..cb3a920 100644 --- a/README.md +++ b/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) diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt index 0c5e2e8..448a4fc 100644 --- a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt +++ b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt @@ -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 /** - * 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 @@ -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<*>? diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceHub.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceHub.kt index 9e285b6..46958c2 100644 --- a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceHub.kt +++ b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceHub.kt @@ -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) \ No newline at end of file diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt index e0a0f59..c5e2f06 100644 --- a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt +++ b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt @@ -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 diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt index c2fdc94..79c06a5 100644 --- a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt +++ b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt @@ -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() private val actions = HashMap() @@ -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 } } diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/DeviceMessage.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceMessage.kt similarity index 98% rename from dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/DeviceMessage.kt rename to dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceMessage.kt index 0adf5ab..9a5aaa5 100644 --- a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/DeviceMessage.kt +++ b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceMessage.kt @@ -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.* diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/MessageController.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/MessageController.kt similarity index 95% rename from dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/MessageController.kt rename to dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/MessageController.kt index aa4421a..2c4a105 100644 --- a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/MessageController.kt +++ b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/MessageController.kt @@ -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 diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/MessageFlow.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/MessageFlow.kt new file mode 100644 index 0000000..e69de29 diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/PropertyFlow.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/PropertyFlow.kt similarity index 86% rename from dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/PropertyFlow.kt rename to dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/PropertyFlow.kt index 8a3d82e..ed45c53 100644 --- a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/PropertyFlow.kt +++ b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/PropertyFlow.kt @@ -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>> = callbackFlow } } } - registerListener(listener, listener) + registerListener(listener) awaitClose { - removeListener(listener) + removeListeners(listener) } } \ No newline at end of file diff --git a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/delegateMappers.kt b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/delegateMappers.kt similarity index 91% rename from dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/delegateMappers.kt rename to dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/delegateMappers.kt index 1827d94..d80ceeb 100644 --- a/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controlers/delegateMappers.kt +++ b/dataforge-control-core/src/commonMain/kotlin/hep/dataforge/control/controllers/delegateMappers.kt @@ -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 diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 6c5eeaa..1cea9ce 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -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().configureEach javafx{ version = "14" modules("javafx.controls") +} + +application{ + mainClassName = "hep.dataforge.control.demo.DemoControllerViewKt" } \ No newline at end of file diff --git a/demo/src/main/kotlin/hep/dataforge/control/demo/DemoDevice.kt b/demo/src/main/kotlin/hep/dataforge/control/demo/DemoDevice.kt index 3d871e0..52b4c56 100644 --- a/demo/src/main/kotlin/hep/dataforge/control/demo/DemoDevice.kt +++ b/demo/src/main/kotlin/hep/dataforge/control/demo/DemoDevice.kt @@ -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) diff --git a/docs/pictures/demo-view.png b/docs/pictures/demo-view.png new file mode 100644 index 0000000..b7df1eb Binary files /dev/null and b/docs/pictures/demo-view.png differ