diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6ad294e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: Gradle build + +on: + push: + branches: [ dev, master ] + pull_request: + +jobs: + build: + runs-on: windows-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3.5.1 + with: + java-version: '11' + distribution: 'liberica' + cache: 'gradle' + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v1.0.4 + - name: Gradle Build + uses: gradle/gradle-build-action@v2.4.2 + with: + arguments: test jvmTest diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index adc74ad..0000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Gradle build - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Build with Gradle - run: ./gradlew build diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..f8b0b86 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,31 @@ +name: Dokka publication + +on: + workflow_dispatch: + release: + types: [ created ] + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 40 + steps: + - uses: actions/checkout@v3.0.0 + - uses: actions/setup-java@v3.0.0 + with: + java-version: 11 + distribution: liberica + - name: Cache konan + uses: actions/cache@v3.0.1 + with: + path: ~/.konan + key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + - uses: gradle/gradle-build-action@v2.4.2 + with: + arguments: dokkaHtmlMultiModule --no-parallel + - uses: JamesIves/github-pages-deploy-action@v4.3.0 + with: + branch: gh-pages + folder: build/dokka/htmlMultiModule diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..31d539c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,50 @@ +name: Gradle publish + +on: + workflow_dispatch: + release: + types: [ created ] + +jobs: + publish: + environment: + name: publish + strategy: + matrix: + os: [ macOS-latest, windows-latest ] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v3.0.0 + - uses: actions/setup-java@v3.10.0 + with: + java-version: 11 + distribution: liberica + - name: Cache konan + uses: actions/cache@v3.0.1 + with: + path: ~/.konan + key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Publish Windows Artifacts + if: matrix.os == 'windows-latest' + uses: gradle/gradle-build-action@v2.4.2 + with: + arguments: | + publishAllPublicationsToSpaceRepository + -Ppublishing.targets=all + -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} + -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} + - name: Publish Mac Artifacts + if: matrix.os == 'macOS-latest' + uses: gradle/gradle-build-action@v2.4.2 + with: + arguments: | + publishMacosX64PublicationToSpaceRepository + publishMacosArm64PublicationToSpaceRepository + publishIosX64PublicationToSpaceRepository + publishIosArm64PublicationToSpaceRepository + publishIosSimulatorArm64PublicationToSpaceRepository + -Ppublishing.targets=all + -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} + -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} diff --git a/.space.kts b/.space.kts new file mode 100644 index 0000000..e4d7522 --- /dev/null +++ b/.space.kts @@ -0,0 +1,3 @@ +job("Build and run tests") { + gradlew("amazoncorretto:17-alpine", "build") +} \ No newline at end of file diff --git a/README.md b/README.md index c2037e9..e52bdac 100644 --- a/README.md +++ b/README.md @@ -3,58 +3,182 @@ # Controls.kt Controls.kt (former 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 +This repository contains a prototype of API and simple implementation of a slow control system, including a demo. -Controls.kt uses some concepts and modules of DataForge, -such as `Meta` (immutable tree-like structure) and `Meta` (which -includes a scalar value, or a tree of values, easily convertable to/from JSON -if needed). +Controls.kt uses some concepts and modules of DataForge, +such as `Meta` (tree-like value structure). 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/) +* [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 +application. Asynchronous operations are implemented with [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) library. ## Materials and publications * Video - [A general overview seminar](https://youtu.be/LO-qjWgXMWc) * Video - [A seminar about the system mechanics](https://youtu.be/wES0RV5GpoQ) -* Article - [A Novel Solution for Controlling Hardware Components of Accelerators and Beamlines](https://www.preprints.org/manuscript/202108.0336/v1) +* Article - [A Novel Solution for Controlling Hardware Components of Accelerators and Beamlines](https://www.preprints.org/manuscript/202108.0336/v1) ### Features Among other things, you can: -- Describe devices and their properties. +- 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 +Example view of a demo: -- `api` - defines API for device management. The main class here is -[`Device`](controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/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). +![](docs/pictures/demo-view.png) -- `base` - contains baseline `Device` implementation -[`DeviceBase`](controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/base/DeviceBase.kt) -and property implementation, including property asynchronous flows. +## Modules + + +### [controls-core](controls-core) +> +> +> **Maturity**: EXPERIMENTAL +> +> **Features:** +> - [device](controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt) : Device API with subscription (asynchronous and pseudo-synchronous properties) +> - [deviceMessage](controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt) : Specification for messages used to communicate between Controls-kt devices. +> - [deviceHub](controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt) : Grouping of devices into local tree-like hubs. + + +### [controls-ktor-tcp](controls-ktor-tcp) +> +> +> **Maturity**: EXPERIMENTAL + +### [controls-magix-client](controls-magix-client) +> +> +> **Maturity**: EXPERIMENTAL + +### [controls-modbus](controls-modbus) +> +> +> **Maturity**: EXPERIMENTAL + +### [controls-opcua](controls-opcua) +> +> +> **Maturity**: EXPERIMENTAL + +### [controls-serial](controls-serial) +> +> +> **Maturity**: EXPERIMENTAL + +### [controls-server](controls-server) +> +> +> **Maturity**: EXPERIMENTAL + +### [controls-storage](controls-storage) +> +> +> **Maturity**: PROTOTYPE + +### [demo](demo) +> +> +> **Maturity**: EXPERIMENTAL + +### [magix](magix) +> +> +> **Maturity**: EXPERIMENTAL + +### [controls-storage/controls-xodus](controls-storage/controls-xodus) +> +> +> **Maturity**: PROTOTYPE + +### [demo/all-things](demo/all-things) +> +> +> **Maturity**: EXPERIMENTAL + +### [demo/car](demo/car) +> +> +> **Maturity**: EXPERIMENTAL + +### [demo/echo](demo/echo) +> +> +> **Maturity**: EXPERIMENTAL + +### [demo/magix-demo](demo/magix-demo) +> +> +> **Maturity**: EXPERIMENTAL + +### [demo/mks-pdr900](demo/mks-pdr900) +> +> +> **Maturity**: EXPERIMENTAL + +### [demo/motors](demo/motors) +> +> +> **Maturity**: EXPERIMENTAL + +### [magix/magix-api](magix/magix-api) +> +> +> **Maturity**: EXPERIMENTAL + +### [magix/magix-java-client](magix/magix-java-client) +> +> +> **Maturity**: EXPERIMENTAL + +### [magix/magix-mqtt](magix/magix-mqtt) +> +> +> **Maturity**: PROTOTYPE + +### [magix/magix-rabbit](magix/magix-rabbit) +> +> +> **Maturity**: PROTOTYPE + +### [magix/magix-rsocket](magix/magix-rsocket) +> +> +> **Maturity**: EXPERIMENTAL + +### [magix/magix-server](magix/magix-server) +> +> +> **Maturity**: EXPERIMENTAL + +### [magix/magix-storage](magix/magix-storage) +> +> +> **Maturity**: EXPERIMENTAL + +### [magix/magix-zmq](magix/magix-zmq) +> +> +> **Maturity**: EXPERIMENTAL + +### [magix/magix-storage/magix-storage-xodus](magix/magix-storage/magix-storage-xodus) +> +> +> **Maturity**: PROTOTYPE -- `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 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/build.gradle.kts b/build.gradle.kts index c9162f9..1e3ae71 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,8 @@ ksciencePublish { space("https://maven.pkg.jetbrains.space/spc/p/controls/maven") } +readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md") + apiValidation { validationDisabled = true } \ No newline at end of file diff --git a/controls-core/build.gradle.kts b/controls-core/build.gradle.kts index abceb3d..b66fc12 100644 --- a/controls-core/build.gradle.kts +++ b/controls-core/build.gradle.kts @@ -18,3 +18,28 @@ kscience { api(spclibs.kotlinx.datetime) } } + + +readme{ + feature("device", ref = "src/commonMain/kotlin/space/kscience/controls/api/Device.kt"){ + """ + Device API with subscription (asynchronous and pseudo-synchronous properties) + """.trimIndent() + } +} + +readme{ + feature("deviceMessage", ref = "src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt"){ + """ + Specification for messages used to communicate between Controls-kt devices. + """.trimIndent() + } +} + +readme{ + feature("deviceHub", ref = "src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt"){ + """ + Grouping of devices into local tree-like hubs. + """.trimIndent() + } +} \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt index c122260..a9dab80 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt @@ -1,6 +1,5 @@ package space.kscience.controls.api -import io.ktor.utils.io.core.Closeable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.cancel @@ -17,10 +16,11 @@ import space.kscience.dataforge.names.Name /** * General interface describing a managed Device. - * Device is a supervisor scope encompassing all operations on a device. When canceled, cancels all running processes. + * [Device] is a supervisor scope encompassing all operations on a device. + * When canceled, cancels all running processes. */ @Type(DEVICE_TARGET) -public interface Device : Closeable, ContextAware, CoroutineScope { +public interface Device : AutoCloseable, ContextAware, CoroutineScope { /** * Initial configuration meta for the device @@ -97,9 +97,8 @@ public suspend fun Device.getOrReadProperty(propertyName: String): Meta = getProperty(propertyName) ?: readProperty(propertyName) /** - * Get a snapshot of logical state of the device + * Get a snapshot of the device logical state * - * TODO currently this */ public fun Device.getAllProperties(): Meta = Meta { for (descriptor in propertyDescriptors) { diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt index a3a5bfd..c1a943a 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt @@ -3,6 +3,7 @@ package space.kscience.controls.manager import kotlinx.coroutines.launch import space.kscience.controls.api.Device import space.kscience.controls.api.DeviceHub +import space.kscience.controls.api.getOrNull import space.kscience.dataforge.context.* import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.MutableMeta @@ -12,6 +13,9 @@ import kotlin.collections.set import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KClass +/** + * DataForge Context plugin that allows to manage devices locally + */ public class DeviceManager : AbstractPlugin(), DeviceHub { override val tag: PluginTag get() = Companion.tag @@ -36,6 +40,9 @@ public class DeviceManager : AbstractPlugin(), DeviceHub { } +/** + * Register and start a device built by [factory] with current [Context] and [meta]. + */ public fun DeviceManager.install(name: String, factory: Factory, meta: Meta = Meta.EMPTY): D { val device = factory(meta, context) registerDevice(NameToken(name), device) @@ -45,6 +52,9 @@ public fun DeviceManager.install(name: String, factory: Factory, return device } +/** + * A delegate that initializes device on the first use + */ public inline fun DeviceManager.installing( factory: Factory, builder: MutableMeta.() -> Unit = {}, @@ -52,7 +62,15 @@ public inline fun DeviceManager.installing( val meta = Meta(builder) return ReadOnlyProperty { _, property -> val name = property.name - install(name, factory, meta) + val current = getOrNull(name) + if (current == null) { + install(name, factory, meta) + } else if (current.meta != meta) { + error("Meta mismatch. Current device meta: ${current.meta}, but factory meta is $meta") + } else { + @Suppress("UNCHECKED_CAST") + current as D + } } } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/deviceMessages.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/respondMessage.kt similarity index 94% rename from controls-core/src/commonMain/kotlin/space/kscience/controls/manager/deviceMessages.kt rename to controls-core/src/commonMain/kotlin/space/kscience/controls/manager/respondMessage.kt index ea5d34c..4db7894 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/deviceMessages.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/respondMessage.kt @@ -9,6 +9,9 @@ import space.kscience.controls.api.* import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.plus +/** + * Process a message targeted at this [Device], assuming its name is [deviceTarget]. + */ public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMessage): DeviceMessage? = try { when (request) { is PropertyGetMessage -> { @@ -66,6 +69,9 @@ public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMess DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = request.sourceDevice) } +/** + * Process incoming [DeviceMessage], using hub naming to evaluate target. + */ public suspend fun DeviceHub.respondHubMessage(request: DeviceMessage): DeviceMessage? { return try { val targetName = request.targetDevice ?: return null @@ -77,7 +83,7 @@ public suspend fun DeviceHub.respondHubMessage(request: DeviceMessage): DeviceMe } /** - * Collect all messages from given [DeviceHub], applying proper relative names + * Collect all messages from given [DeviceHub], applying proper relative names. */ public fun DeviceHub.hubMessageFlow(scope: CoroutineScope): Flow { diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Port.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Port.kt index 374c404..4fad1db 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Port.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Port.kt @@ -9,8 +9,14 @@ import space.kscience.dataforge.context.* import space.kscience.dataforge.misc.Type import kotlin.coroutines.CoroutineContext +/** + * Raw [ByteArray] port + */ public interface Port : ContextAware, Socket +/** + * A specialized factory for [Port] + */ @Type(PortFactory.TYPE) public interface PortFactory: Factory{ public val type: String @@ -20,6 +26,9 @@ public interface PortFactory: Factory{ } } +/** + * Common abstraction for [Port] based on [Channel] + */ public abstract class AbstractPort( override val context: Context, coroutineContext: CoroutineContext = context.coroutineContext, @@ -72,7 +81,7 @@ public abstract class AbstractPort( /** * Raw flow of incoming data chunks. The chunks are not guaranteed to be complete phrases. - * In order to form phrases some condition should be used on top of it. + * In order to form phrases, some condition should be used on top of it. * For example [delimitedIncoming] generates phrases with fixed delimiter. */ override fun receiving(): Flow = incoming.receiveAsFlow() diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Ports.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Ports.kt index 8c652cc..6d5c6d9 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Ports.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Ports.kt @@ -5,6 +5,9 @@ import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.string import kotlin.reflect.KClass +/** + * A DataForge plugin for managing ports + */ public class Ports : AbstractPlugin() { override val tag: PluginTag get() = Companion.tag @@ -15,6 +18,9 @@ public class Ports : AbstractPlugin() { private val portCache = mutableMapOf() + /** + * Create a new [Port] according to specification + */ public fun buildPort(meta: Meta): Port = portCache.getOrPut(meta) { val type by meta.string { error("Port type is not defined") } val factory = portFactories.values.firstOrNull { it.type == type } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt index d1fd9b7..4546975 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt @@ -14,6 +14,9 @@ import space.kscience.dataforge.meta.Meta import kotlin.coroutines.CoroutineContext +/** + * A base abstractions for [Device], introducing specifications for properties + */ @OptIn(InternalDeviceAPI::class) public abstract class DeviceBase>( override val context: Context = Global, diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt index b545910..4fff906 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt @@ -27,7 +27,7 @@ public interface DevicePropertySpec { public val descriptor: PropertyDescriptor /** - * Meta item converter for resulting type + * Meta item converter for the resulting type */ public val converter: MetaConverter @@ -78,7 +78,7 @@ public interface DeviceActionSpec { } /** - * Action name, should be unique in device + * Action name. Should be unique in the device */ public val DeviceActionSpec<*, *, *>.name: String get() = descriptor.name diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt index 83364ee..966ee32 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt @@ -12,6 +12,8 @@ import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 + + @OptIn(InternalDeviceAPI::class) public abstract class DeviceSpec { //initializing meta property for everyone diff --git a/controls-ktor-tcp/README.md b/controls-ktor-tcp/README.md new file mode 100644 index 0000000..94deb3b --- /dev/null +++ b/controls-ktor-tcp/README.md @@ -0,0 +1,4 @@ +# Module controls-ktor-tcp + + + diff --git a/controls-magix-client/README.md b/controls-magix-client/README.md new file mode 100644 index 0000000..2c1895e --- /dev/null +++ b/controls-magix-client/README.md @@ -0,0 +1,32 @@ +# Module controls-magix-client + + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-magix-client:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:controls-magix-client:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-magix-client:0.1.1-SNAPSHOT") +} +``` diff --git a/controls-modbus/README.md b/controls-modbus/README.md new file mode 100644 index 0000000..f6d2335 --- /dev/null +++ b/controls-modbus/README.md @@ -0,0 +1,4 @@ +# Module controls-modbus + +A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols + diff --git a/controls-modbus/build.gradle.kts b/controls-modbus/build.gradle.kts new file mode 100644 index 0000000..8734460 --- /dev/null +++ b/controls-modbus/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("space.kscience.gradle.jvm") +} + +description = """ + A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols +""".trimIndent() + + +dependencies { + api(projects.controlsCore) + api("com.ghgande:j2mod:3.1.1") +} diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt new file mode 100644 index 0000000..04bb6a0 --- /dev/null +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt @@ -0,0 +1,146 @@ +package space.kscience.controls.modbus + +import com.ghgande.j2mod.modbus.facade.AbstractModbusMaster +import com.ghgande.j2mod.modbus.procimg.InputRegister +import com.ghgande.j2mod.modbus.procimg.Register +import com.ghgande.j2mod.modbus.procimg.SimpleRegister +import com.ghgande.j2mod.modbus.util.BitVector +import space.kscience.controls.api.Device +import java.nio.ByteBuffer +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + + +/** + * A Modbus device backed by j2mod client + */ +public interface ModbusDevice : Device { + + /** + * Client id for this specific device + */ + public val clientId: Int + + /** + * The OPC-UA client initialized on first use + */ + public val master: AbstractModbusMaster +} + +/** + * Read multiple sequential modbus coils (bit-values) + */ +public fun ModbusDevice.readCoils(ref: Int, count: Int): BitVector = + master.readCoils(clientId, ref, count) + +public fun ModbusDevice.readCoil(ref: Int): Boolean = + master.readCoils(clientId, ref, 1).getBit(0) + +public fun ModbusDevice.writeCoils(ref: Int, values: BooleanArray) { + val bitVector = BitVector(values.size) + values.forEachIndexed { index, value -> + bitVector.setBit(index, value) + } + master.writeMultipleCoils(clientId, ref, bitVector) +} + +public fun ModbusDevice.writeCoil(ref: Int, value: Boolean) { + master.writeCoil(clientId, ref, value) +} + +public fun ModbusDevice.readInputDiscretes(ref: Int, count: Int): BitVector = + master.readInputDiscretes(clientId, ref, count) + +public fun ModbusDevice.readInputRegisters(ref: Int, count: Int): List = + master.readInputRegisters(clientId, ref, count).toList() + +private fun Array.toBuffer(): ByteBuffer { + val buffer: ByteBuffer = ByteBuffer.allocate(size * 2) + forEachIndexed { index, value -> + buffer.position(index * 2) + buffer.put(value.toBytes()) + } + buffer.flip() + return buffer +} + +public fun ModbusDevice.readInputRegistersToBuffer(ref: Int, count: Int): ByteBuffer = + master.readInputRegisters(clientId, ref, count).toBuffer() + +public fun ModbusDevice.readDoubleInput(ref: Int): Double = + readInputRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble() + +public fun ModbusDevice.readShortInput(ref: Int): Short = + readInputRegisters(ref, 1).first().toShort() + +public fun ModbusDevice.readHoldingRegisters(ref: Int, count: Int): List = + master.readMultipleRegisters(clientId, ref, count).toList() + +public fun ModbusDevice.readHoldingRegistersToBuffer(ref: Int, count: Int): ByteBuffer = + master.readMultipleRegisters(clientId, ref, count).toBuffer() + +public fun ModbusDevice.readDoubleRegister(ref: Int): Double = + readHoldingRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble() + +public fun ModbusDevice.readShortRegister(ref: Int): Short = + readHoldingRegisters(ref, 1).first().toShort() + +public fun ModbusDevice.writeHoldingRegisters(ref: Int, values: ShortArray): Int = + master.writeMultipleRegisters( + clientId, + ref, + Array(values.size) { SimpleRegister().apply { setValue(values[it]) } } + ) + +public fun ModbusDevice.writeHoldingRegister(ref: Int, value: Short): Int = + master.writeSingleRegister( + clientId, + ref, + SimpleRegister().apply { setValue(value) } + ) + +public fun ModbusDevice.writeHoldingRegisters(ref: Int, buffer: ByteBuffer): Int { + val array = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) } + + return writeHoldingRegisters(ref, array) +} + +public fun ModbusDevice.writeShortRegister(ref: Int, value: Short) { + master.writeSingleRegister(ref, SimpleRegister().apply { setValue(value) }) +} + +public fun ModbusDevice.modBusRegister( + ref: Int, +): ReadWriteProperty = object : ReadWriteProperty { + override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Short = readShortRegister(ref) + + override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Short) { + writeHoldingRegister(ref, value) + } +} + +public fun ModbusDevice.modBusDoubleRegister( + ref: Int, +): ReadWriteProperty = object : ReadWriteProperty { + override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Double = readDoubleRegister(ref) + + override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Double) { + val buffer = ByteBuffer.allocate(Double.SIZE_BYTES).apply { putDouble(value) } + writeHoldingRegisters(ref, buffer) + } +} + + +// +//public inline fun ModbusDevice.opcDouble( +//): ReadWriteProperty = ma +// +//public inline fun ModbusDeviceBySpec<*>.opcInt( +// nodeId: NodeId, +// magAge: Double = 1.0, +//): ReadWriteProperty = opc(nodeId, MetaConverter.int, magAge) +// +//public inline fun ModbusDeviceBySpec<*>.opcString( +// nodeId: NodeId, +// magAge: Double = 1.0, +//): ReadWriteProperty = opc(nodeId, MetaConverter.string, magAge) diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt new file mode 100644 index 0000000..cc6044a --- /dev/null +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt @@ -0,0 +1,45 @@ +package space.kscience.controls.modbus + +import com.ghgande.j2mod.modbus.facade.AbstractModbusMaster +import space.kscience.controls.api.DeviceHub +import space.kscience.controls.spec.DeviceBySpec +import space.kscience.controls.spec.DeviceSpec +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.names.NameToken + +/** + * A variant of [DeviceBySpec] that includes Modbus RTU/TCP/UDP client + */ +public open class ModbusDeviceBySpec( + context: Context, + spec: DeviceSpec, + override val clientId: Int, + override val master: AbstractModbusMaster, + meta: Meta = Meta.EMPTY, +) : ModbusDevice, DeviceBySpec(spec, context, meta) + + +public class ModbusHub( + public val context: Context, + public val masterBuilder: () -> AbstractModbusMaster, + public val specs: Map>>, +) : DeviceHub, AutoCloseable { + + public val master: AbstractModbusMaster by lazy(masterBuilder) + + override val devices: Map by lazy { + specs.mapValues { (_, pair) -> + ModbusDeviceBySpec( + context, + pair.second, + pair.first, + master + ) + } + } + + override fun close() { + master.disconnect() + } +} diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt new file mode 100644 index 0000000..e365a88 --- /dev/null +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt @@ -0,0 +1,44 @@ +package space.kscience.controls.modbus + + +public sealed class ModbusRegistryKey { + /** + * Read-only boolean value + */ + public class Coil(public val address: Int) : ModbusRegistryKey() { + init { + require(address in 1..9999) { "Coil address must be in 1..9999 range" } + } + } + + /** + * Read-write boolean value + */ + public class DiscreteInput(public val address: Int) : ModbusRegistryKey() { + init { + require(address in 10001..19999) { "DiscreteInput address must be in 10001..19999 range" } + } + } + + public class InputRegister(public val address: Int) : ModbusRegistryKey() { + init { + require(address in 20001..29999) { "InputRegister address must be in 20001..29999 range" } + } + } + + public class HoldingRegister(public val address: Int) : ModbusRegistryKey() { + init { + require(address in 30001..39999) { "HoldingRegister address must be in 30001..39999 range" } + } + } +} + +public abstract class ModbusRegistryMap { + protected fun coil(address: Int): ModbusRegistryKey.Coil = ModbusRegistryKey.Coil(address) + + protected fun discrete(address: Int): ModbusRegistryKey.DiscreteInput = ModbusRegistryKey.DiscreteInput(address) + + protected fun input(address: Int): ModbusRegistryKey.InputRegister = ModbusRegistryKey.InputRegister(address) + + protected fun register(address: Int): ModbusRegistryKey.HoldingRegister = ModbusRegistryKey.HoldingRegister(address) +} diff --git a/controls-opcua/README.md b/controls-opcua/README.md new file mode 100644 index 0000000..3accf5b --- /dev/null +++ b/controls-opcua/README.md @@ -0,0 +1,4 @@ +# Module controls-opcua + + + diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MiloDeviceBySpec.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MiloDeviceBySpec.kt index 2a130d7..8fef3c1 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MiloDeviceBySpec.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MiloDeviceBySpec.kt @@ -8,10 +8,7 @@ import space.kscience.controls.spec.DeviceBySpec import space.kscience.controls.spec.DeviceSpec import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Global -import space.kscience.dataforge.meta.Scheme -import space.kscience.dataforge.meta.SchemeSpec -import space.kscience.dataforge.meta.specOrNull -import space.kscience.dataforge.meta.string +import space.kscience.dataforge.meta.* public sealed class MiloIdentity: Scheme() @@ -35,6 +32,8 @@ public class MiloConfiguration : Scheme() { public var username: MiloUsername? by specOrNull(MiloUsername) + public var securityPolicy: SecurityPolicy by enum(SecurityPolicy.None) + public companion object : SchemeSpec(::MiloConfiguration) } @@ -50,7 +49,7 @@ public open class MiloDeviceBySpec>( override val client: OpcUaClient by lazy { context.createMiloClient( config.endpointUrl, - securityPolicy = SecurityPolicy.None, + securityPolicy = config.securityPolicy, identityProvider = config.username?.let { UsernameProvider(it.username,it.password) } ?: AnonymousProvider() diff --git a/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt b/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt index 88d7e8d..72cee67 100644 --- a/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt +++ b/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt @@ -4,7 +4,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId import org.junit.jupiter.api.Test -import space.kscience.controls.opcua.client.OpcUaClientTest.DemoMiloDevice.Companion.randomDouble import space.kscience.controls.spec.DeviceSpec import space.kscience.controls.spec.doubleProperty import space.kscience.dataforge.meta.transformations.MetaConverter @@ -21,10 +20,6 @@ class OpcUaClientTest { fun build(): DemoMiloDevice { val config = MiloConfiguration { endpointUrl = "opc.tcp://milo.digitalpetri.com:62541/milo" -// username = MiloUsername{ -// username = "user1" -// password = "password" -// } } return DemoMiloDevice(config) } @@ -41,7 +36,7 @@ class OpcUaClientTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun testReadDouble() = runTest { - println(DemoMiloDevice.use { randomDouble.read() }) + println(DemoMiloDevice.use { DemoMiloDevice.randomDouble.read() }) } } \ No newline at end of file diff --git a/controls-serial/README.md b/controls-serial/README.md new file mode 100644 index 0000000..eb214ba --- /dev/null +++ b/controls-serial/README.md @@ -0,0 +1,32 @@ +# Module controls-serial + + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-serial:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:controls-serial:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-serial:0.1.1-SNAPSHOT") +} +``` diff --git a/controls-server/README.md b/controls-server/README.md new file mode 100644 index 0000000..561a815 --- /dev/null +++ b/controls-server/README.md @@ -0,0 +1,32 @@ +# Module controls-server + +A magix event loop server with web server for visualization. + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-server:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:controls-server:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-server:0.1.1-SNAPSHOT") +} +``` diff --git a/controls-storage/README.md b/controls-storage/README.md index 14289a1..650dd53 100644 --- a/controls-storage/README.md +++ b/controls-storage/README.md @@ -1,12 +1,32 @@ -# Description +# Module controls-storage -This module provides API to store [DeviceMessages](/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/api/DeviceMessage.kt) -from certain [DeviceManager](/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/DeviceManager.kt) -or [MagixMessages](magix/magix-api/src/commonMain/kotlin/ru/mipt/npm/magix/api/MagixMessage.kt) -from certain [magix server](/magix/magix-server/src/main/kotlin/ru/mipt/npm/magix/server/server.kt). -# Usage -All usage examples can be found in [VirtualCarController](/demo/car/src/main/kotlin/ru/mipt/npm/controls/demo/car/VirtualCarController.kt). +## Usage -For more details, you can see comments in source code of this module. +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-storage:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:controls-storage:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-storage:0.1.1-SNAPSHOT") +} +``` diff --git a/controls-storage/controls-xodus/README.md b/controls-storage/controls-xodus/README.md new file mode 100644 index 0000000..a25650e --- /dev/null +++ b/controls-storage/controls-xodus/README.md @@ -0,0 +1,32 @@ +# Module controls-xodus + + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-xodus:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:controls-xodus:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-xodus:0.1.1-SNAPSHOT") +} +``` diff --git a/controls-storage/controls-xodus/src/main/kotlin/space/kscience/controls/xodus/XodusDeviceMessageStorage.kt b/controls-storage/controls-xodus/src/main/kotlin/space/kscience/controls/xodus/XodusDeviceMessageStorage.kt index 72cdd1f..bccaee9 100644 --- a/controls-storage/controls-xodus/src/main/kotlin/space/kscience/controls/xodus/XodusDeviceMessageStorage.kt +++ b/controls-storage/controls-xodus/src/main/kotlin/space/kscience/controls/xodus/XodusDeviceMessageStorage.kt @@ -16,7 +16,7 @@ import space.kscience.controls.storage.DeviceMessageStorage import space.kscience.controls.storage.workDirectory import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Factory -import space.kscience.dataforge.context.fetch +import space.kscience.dataforge.context.request import space.kscience.dataforge.io.IOPlugin import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.get @@ -112,7 +112,7 @@ public class XodusDeviceMessageStorage( public val XODUS_STORE_PROPERTY: Name = Name.of("xodus", "storagePath") override fun build(context: Context, meta: Meta): XodusDeviceMessageStorage { - val io = context.fetch(IOPlugin) + val io = context.request(IOPlugin) val storePath = io.workDirectory.resolve( meta[XODUS_STORE_PROPERTY]?.string ?: context.properties[XODUS_STORE_PROPERTY]?.string ?: "storage" diff --git a/controls-storage/src/jvmMain/kotlin/space/kscience/controls/storage/workDirectory.kt b/controls-storage/src/jvmMain/kotlin/space/kscience/controls/storage/workDirectory.kt index 7e4086f..0847055 100644 --- a/controls-storage/src/jvmMain/kotlin/space/kscience/controls/storage/workDirectory.kt +++ b/controls-storage/src/jvmMain/kotlin/space/kscience/controls/storage/workDirectory.kt @@ -8,9 +8,6 @@ import space.kscience.dataforge.meta.string import java.nio.file.Path import kotlin.io.path.Path -//TODO remove on DF 0.6 - -internal val IOPlugin.Companion.WORK_DIRECTORY_KEY: String get() = ".dataforge" public val IOPlugin.workDirectory: Path get() { diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..e2a13c4 --- /dev/null +++ b/demo/README.md @@ -0,0 +1,4 @@ +# Module demo + + + diff --git a/demo/all-things/README.md b/demo/all-things/README.md new file mode 100644 index 0000000..5d9c12f --- /dev/null +++ b/demo/all-things/README.md @@ -0,0 +1,4 @@ +# Module all-things + + + diff --git a/demo/all-things/build.gradle.kts b/demo/all-things/build.gradle.kts index b135253..1ea3c05 100644 --- a/demo/all-things/build.gradle.kts +++ b/demo/all-things/build.gradle.kts @@ -24,9 +24,9 @@ dependencies { implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("no.tornado:tornadofx:1.7.20") - implementation("space.kscience:plotlykt-server:0.5.3-dev-1") + implementation("space.kscience:plotlykt-server:0.5.3") // implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") - implementation("ch.qos.logback:logback-classic:1.2.11") + implementation(spclibs.logback.classic) } kotlin{ diff --git a/demo/car/README.md b/demo/car/README.md new file mode 100644 index 0000000..8c7ee00 --- /dev/null +++ b/demo/car/README.md @@ -0,0 +1,4 @@ +# Module car + + + diff --git a/demo/echo/README.md b/demo/echo/README.md new file mode 100644 index 0000000..4f3696a --- /dev/null +++ b/demo/echo/README.md @@ -0,0 +1,4 @@ +# Module echo + + + diff --git a/demo/magix-demo/README.md b/demo/magix-demo/README.md new file mode 100644 index 0000000..f27a6d3 --- /dev/null +++ b/demo/magix-demo/README.md @@ -0,0 +1,4 @@ +# Module magix-demo + + + diff --git a/demo/mks-pdr900/README.md b/demo/mks-pdr900/README.md new file mode 100644 index 0000000..e0e366c --- /dev/null +++ b/demo/mks-pdr900/README.md @@ -0,0 +1,4 @@ +# Module mks-pdr900 + + + diff --git a/demo/motors/README.md b/demo/motors/README.md new file mode 100644 index 0000000..2563da8 --- /dev/null +++ b/demo/motors/README.md @@ -0,0 +1,4 @@ +# Module motors + + + diff --git a/docs/templates/ARTIFACT-TEMPLATE.md b/docs/templates/ARTIFACT-TEMPLATE.md new file mode 100644 index 0000000..a3e47e6 --- /dev/null +++ b/docs/templates/ARTIFACT-TEMPLATE.md @@ -0,0 +1,30 @@ +## Artifact: + +The Maven coordinates of this project are `${group}:${name}:${version}`. + +**Gradle:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() + // development and snapshot versions + maven { url 'https://maven.pkg.jetbrains.space/spc/p/sci/dev' } +} + +dependencies { + implementation '${group}:${name}:${version}' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() + // development and snapshot versions + maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") +} + +dependencies { + implementation("${group}:${name}:${version}") +} +``` \ No newline at end of file diff --git a/docs/templates/README-TEMPLATE.md b/docs/templates/README-TEMPLATE.md new file mode 100644 index 0000000..57f30c1 --- /dev/null +++ b/docs/templates/README-TEMPLATE.md @@ -0,0 +1,48 @@ +[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) + +# Controls.kt + +Controls.kt (former 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. + +Controls.kt uses some concepts and modules of DataForge, +such as `Meta` (tree-like value structure). + +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. + +## Materials and publications + +* Video - [A general overview seminar](https://youtu.be/LO-qjWgXMWc) +* Video - [A seminar about the system mechanics](https://youtu.be/wES0RV5GpoQ) +* Article - [A Novel Solution for Controlling Hardware Components of Accelerators and Beamlines](https://www.preprints.org/manuscript/202108.0336/v1) + +### 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. + +Example view of a demo: + +![](docs/pictures/demo-view.png) + +## Modules + +${modules} + +### `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. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 59bc51a..fae0804 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/magix/README.md b/magix/README.md new file mode 100644 index 0000000..de1d7a3 --- /dev/null +++ b/magix/README.md @@ -0,0 +1,4 @@ +# Module magix + + + diff --git a/magix/magix-api/README.md b/magix/magix-api/README.md new file mode 100644 index 0000000..0226fc6 --- /dev/null +++ b/magix/magix-api/README.md @@ -0,0 +1,32 @@ +# Module magix-api + + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-api:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:magix-api:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-api:0.1.1-SNAPSHOT") +} +``` diff --git a/magix/magix-java-client/README.md b/magix/magix-java-client/README.md new file mode 100644 index 0000000..375d6df --- /dev/null +++ b/magix/magix-java-client/README.md @@ -0,0 +1,32 @@ +# Module magix-java-client + + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-java-client:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:magix-java-client:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-java-client:0.1.1-SNAPSHOT") +} +``` diff --git a/magix/magix-mqtt/README.md b/magix/magix-mqtt/README.md new file mode 100644 index 0000000..d8b9da6 --- /dev/null +++ b/magix/magix-mqtt/README.md @@ -0,0 +1,32 @@ +# Module magix-mqtt + +MQTT client magix endpoint + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-mqtt:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:magix-mqtt:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-mqtt:0.1.1-SNAPSHOT") +} +``` diff --git a/magix/magix-rabbit/README.md b/magix/magix-rabbit/README.md new file mode 100644 index 0000000..7c60864 --- /dev/null +++ b/magix/magix-rabbit/README.md @@ -0,0 +1,32 @@ +# Module magix-rabbit + +RabbitMQ client magix endpoint + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-rabbit:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:magix-rabbit:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-rabbit:0.1.1-SNAPSHOT") +} +``` diff --git a/magix/magix-rsocket/README.md b/magix/magix-rsocket/README.md new file mode 100644 index 0000000..0d5c9e6 --- /dev/null +++ b/magix/magix-rsocket/README.md @@ -0,0 +1,32 @@ +# Module magix-rsocket + +Magix endpoint (client) based on RSocket + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-rsocket:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:magix-rsocket:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-rsocket:0.1.1-SNAPSHOT") +} +``` diff --git a/magix/magix-server/README.md b/magix/magix-server/README.md new file mode 100644 index 0000000..f5a83e3 --- /dev/null +++ b/magix/magix-server/README.md @@ -0,0 +1,32 @@ +# Module magix-server + +A magix event loop implementation in Kotlin. Includes HTTP/SSE and RSocket routes. + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-server:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:magix-server:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-server:0.1.1-SNAPSHOT") +} +``` diff --git a/magix/magix-storage/README.md b/magix/magix-storage/README.md new file mode 100644 index 0000000..7ebafae --- /dev/null +++ b/magix/magix-storage/README.md @@ -0,0 +1,4 @@ +# Module magix-storage + + + diff --git a/magix/magix-storage/magix-storage-xodus/README.md b/magix/magix-storage/magix-storage-xodus/README.md new file mode 100644 index 0000000..57107c5 --- /dev/null +++ b/magix/magix-storage/magix-storage-xodus/README.md @@ -0,0 +1,32 @@ +# Module magix-storage-xodus + + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-storage-xodus:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:magix-storage-xodus:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-storage-xodus:0.1.1-SNAPSHOT") +} +``` diff --git a/magix/magix-zmq/README.md b/magix/magix-zmq/README.md new file mode 100644 index 0000000..cf150fe --- /dev/null +++ b/magix/magix-zmq/README.md @@ -0,0 +1,32 @@ +# Module magix-zmq + +ZMQ client endpoint for Magix + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-zmq:0.1.1-SNAPSHOT`. + +**Gradle Groovy:** +```groovy +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:magix-zmq:0.1.1-SNAPSHOT' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-zmq:0.1.1-SNAPSHOT") +} +``` diff --git a/settings.gradle.kts b/settings.gradle.kts index c66ec81..a255044 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -45,6 +45,7 @@ include( ":controls-serial", ":controls-server", ":controls-opcua", + ":controls-modbus", // ":controls-mongo", ":controls-storage", ":controls-storage:controls-xodus",