[WIP] Refactor constructor
This commit is contained in:
parent
a9592d0372
commit
5921978122
10
README.md
10
README.md
@ -104,6 +104,11 @@ Automatically checks consistency.
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [controls-plc4x](controls-plc4x)
|
||||
> A plugin for Controls-kt device server on top of plc4x library
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [controls-ports-ktor](controls-ports-ktor)
|
||||
> Implementation of byte ports on top os ktor-io asynchronous API
|
||||
>
|
||||
@ -209,6 +214,11 @@ Automatically checks consistency.
|
||||
>
|
||||
> **Maturity**: PROTOTYPE
|
||||
|
||||
### [magix/magix-utils](magix/magix-utils)
|
||||
> Common utilities and services for Magix endpoints.
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [magix/magix-zmq](magix/magix-zmq)
|
||||
> ZMQ client endpoint for Magix
|
||||
>
|
||||
|
@ -6,7 +6,7 @@ A low-code constructor for composite devices simulation
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-constructor:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-constructor:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-constructor:0.3.0")
|
||||
implementation("space.kscience:controls-constructor:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -2,7 +2,10 @@ package space.kscience.controls.constructor
|
||||
|
||||
import space.kscience.controls.api.Device
|
||||
|
||||
public sealed interface ConstructorBinding
|
||||
/**
|
||||
* A binding that is used to describe device functionality
|
||||
*/
|
||||
public sealed interface Binding
|
||||
|
||||
/**
|
||||
* A binding that exposes device property as read-only state
|
||||
@ -11,16 +14,22 @@ public class PropertyBinding<T>(
|
||||
public val device: Device,
|
||||
public val propertyName: String,
|
||||
public val state: DeviceState<T>,
|
||||
) : ConstructorBinding
|
||||
) : Binding
|
||||
|
||||
/**
|
||||
* A binding for independent state like a timer
|
||||
*/
|
||||
public class StateBinding<T>(
|
||||
public val state: DeviceState<T>
|
||||
) : ConstructorBinding
|
||||
) : Binding
|
||||
|
||||
public class ActionBinding(
|
||||
public val reads: Collection<DeviceState<*>>,
|
||||
public val writes: Collection<DeviceState<*>>
|
||||
): ConstructorBinding
|
||||
): Binding
|
||||
|
||||
|
||||
public interface BindingsContainer{
|
||||
public val bindings: List<Binding>
|
||||
public fun registerBinding(binding: Binding)
|
||||
}
|
@ -26,11 +26,11 @@ import kotlin.time.Duration
|
||||
public abstract class DeviceConstructor(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : DeviceGroup(context, meta) {
|
||||
private val _bindings: MutableList<ConstructorBinding> = mutableListOf()
|
||||
public val bindings: List<ConstructorBinding> get() = _bindings
|
||||
) : DeviceGroup(context, meta), BindingsContainer {
|
||||
private val _bindings: MutableList<Binding> = mutableListOf()
|
||||
override val bindings: List<Binding> get() = _bindings
|
||||
|
||||
public fun registerBinding(binding: ConstructorBinding) {
|
||||
override fun registerBinding(binding: Binding) {
|
||||
_bindings.add(binding)
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ public abstract class DeviceConstructor(
|
||||
.also { registerBinding(StateBinding(it)) }
|
||||
|
||||
/**
|
||||
* Launch action that is performed on each [DeviceState] value change.
|
||||
* Bind an action to a [DeviceState]. [onChange] block is performed on each state change
|
||||
*
|
||||
* Optionally provide [writes] - a set of states that this change affects.
|
||||
*/
|
||||
|
@ -0,0 +1,12 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
public abstract class DeviceModel : BindingsContainer {
|
||||
|
||||
private val _bindings: MutableList<Binding> = mutableListOf()
|
||||
|
||||
override val bindings: List<Binding> get() = _bindings
|
||||
|
||||
override fun registerBinding(binding: Binding) {
|
||||
_bindings.add(binding)
|
||||
}
|
||||
}
|
@ -63,25 +63,25 @@ public interface DeviceStateWithDependencies<T> : DeviceState<T> {
|
||||
/**
|
||||
* Create a new read-only [DeviceState] that mirrors receiver state by mapping the value with [mapper].
|
||||
*/
|
||||
public fun <T, R> DeviceState<T>.map(
|
||||
public fun <T, R> DeviceState.Companion.map(
|
||||
state: DeviceState<T>,
|
||||
converter: MetaConverter<R>, mapper: (T) -> R,
|
||||
): DeviceStateWithDependencies<R> = object : DeviceStateWithDependencies<R> {
|
||||
override val dependencies = listOf(this)
|
||||
override val dependencies = listOf(state)
|
||||
|
||||
override val converter: MetaConverter<R> = converter
|
||||
|
||||
override val value: R
|
||||
get() = mapper(this@map.value)
|
||||
override val value: R get() = mapper(state.value)
|
||||
|
||||
override val valueFlow: Flow<R> = this@map.valueFlow.map(mapper)
|
||||
override val valueFlow: Flow<R> = state.valueFlow.map(mapper)
|
||||
|
||||
override fun toString(): String = "DeviceState.map(arg=${this@map}, converter=$converter)"
|
||||
override fun toString(): String = "DeviceState.map(arg=${state}, converter=$converter)"
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine two device states into one read-only [DeviceState]. Only the latest value of each state is used.
|
||||
*/
|
||||
public fun <T1, T2, R> combine(
|
||||
public fun <T1, T2, R> DeviceState.Companion.combine(
|
||||
state1: DeviceState<T1>,
|
||||
state2: DeviceState<T2>,
|
||||
converter: MetaConverter<R>,
|
||||
|
@ -1,5 +1,6 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
@ -26,9 +27,9 @@ public class TimerState(
|
||||
|
||||
private val clock = MutableStateFlow(clockManager.clock.now())
|
||||
|
||||
private val updateJob = clockManager.context.launch {
|
||||
private val updateJob = clockManager.context.launch(clockManager.asDispatcher()) {
|
||||
while (isActive) {
|
||||
clockManager.delay(tick)
|
||||
delay(tick)
|
||||
clock.value = clockManager.clock.now()
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import space.kscience.dataforge.meta.MetaConverter
|
||||
/**
|
||||
* A state describing a [Double] value in the [range]
|
||||
*/
|
||||
public class DoubleRangeState(
|
||||
public class DoubleInRangeState(
|
||||
initialValue: Double,
|
||||
public val range: ClosedFloatingPointRange<Double>,
|
||||
) : MutableDeviceState<Double> {
|
||||
@ -32,20 +32,28 @@ public class DoubleRangeState(
|
||||
/**
|
||||
* A state showing that the range is on its lower boundary
|
||||
*/
|
||||
public val atStartState: DeviceState<Boolean> = map(MetaConverter.boolean) { it <= range.start }
|
||||
public val atStart: DeviceState<Boolean> = DeviceState.map(this, MetaConverter.boolean) {
|
||||
it <= range.start
|
||||
}
|
||||
|
||||
/**
|
||||
* A state showing that the range is on its higher boundary
|
||||
*/
|
||||
public val atEndState: DeviceState<Boolean> = map(MetaConverter.boolean) { it >= range.endInclusive }
|
||||
public val atEnd: DeviceState<Boolean> = DeviceState.map(this, MetaConverter.boolean) {
|
||||
it >= range.endInclusive
|
||||
}
|
||||
|
||||
override fun toString(): String = "DoubleRangeState(range=$range, converter=$converter)"
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
public fun DeviceGroup.rangeState(
|
||||
/**
|
||||
* Create and register a [DoubleInRangeState]
|
||||
*/
|
||||
public fun BindingsContainer.doubleInRangeState(
|
||||
initialValue: Double,
|
||||
range: ClosedFloatingPointRange<Double>,
|
||||
): DoubleRangeState = DoubleRangeState(initialValue, range)
|
||||
): DoubleInRangeState = DoubleInRangeState(initialValue, range).also {
|
||||
registerBinding(StateBinding(it))
|
||||
}
|
@ -25,8 +25,6 @@ private class VirtualDeviceState<T>(
|
||||
}
|
||||
|
||||
override fun toString(): String = "VirtualDeviceState(converter=$converter)"
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ Core interfaces for building a device server
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-core:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-core:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -26,6 +26,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-core:0.3.0")
|
||||
implementation("space.kscience:controls-core:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -1,24 +1,80 @@
|
||||
package space.kscience.controls.manager
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.datetime.Clock
|
||||
import space.kscience.dataforge.context.AbstractPlugin
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import kotlinx.datetime.Instant
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.dataforge.context.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import kotlin.time.Duration
|
||||
import space.kscience.dataforge.meta.double
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
private class CompressedTimeDispatcher(
|
||||
val dispatcher: CoroutineDispatcher,
|
||||
val compression: Double,
|
||||
) : CoroutineDispatcher(), Delay {
|
||||
|
||||
@InternalCoroutinesApi
|
||||
override fun dispatchYield(context: CoroutineContext, block: Runnable) {
|
||||
dispatcher.dispatchYield(context, block)
|
||||
}
|
||||
|
||||
override fun isDispatchNeeded(context: CoroutineContext): Boolean = dispatcher.isDispatchNeeded(context)
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun limitedParallelism(parallelism: Int): CoroutineDispatcher = dispatcher.limitedParallelism(parallelism)
|
||||
|
||||
override fun dispatch(context: CoroutineContext, block: Runnable) {
|
||||
dispatcher.dispatch(context, block)
|
||||
}
|
||||
|
||||
private val delay = ((dispatcher as? Delay) ?: (Dispatchers.Default as Delay))
|
||||
|
||||
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
|
||||
delay.scheduleResumeAfterDelay((timeMillis / compression).roundToLong(), continuation)
|
||||
}
|
||||
|
||||
|
||||
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
|
||||
return delay.invokeOnTimeout((timeMillis / compression).roundToLong(), block, context)
|
||||
}
|
||||
}
|
||||
|
||||
private class CompressedClock(
|
||||
val start: Instant,
|
||||
val compression: Double,
|
||||
val baseClock: Clock = Clock.System,
|
||||
) : Clock {
|
||||
override fun now(): Instant {
|
||||
val elapsed = (baseClock.now() - start)
|
||||
return start + elapsed / compression
|
||||
}
|
||||
}
|
||||
|
||||
public class ClockManager : AbstractPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
public val timeCompression: Double by meta.double(1.0)
|
||||
|
||||
public val clock: Clock by lazy {
|
||||
//TODO add clock customization
|
||||
Clock.System
|
||||
if (timeCompression == 1.0) {
|
||||
Clock.System
|
||||
} else {
|
||||
CompressedClock(Clock.System.now(), timeCompression)
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun delay(duration: Duration) {
|
||||
//TODO add time compression
|
||||
kotlinx.coroutines.delay(duration)
|
||||
/**
|
||||
* Provide a [CoroutineDispatcher] with compressed time based on given [dispatcher]
|
||||
*/
|
||||
public fun asDispatcher(
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
): CoroutineDispatcher = if (timeCompression == 1.0) {
|
||||
dispatcher
|
||||
} else {
|
||||
CompressedTimeDispatcher(dispatcher, timeCompression)
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<ClockManager> {
|
||||
@ -29,3 +85,15 @@ public class ClockManager : AbstractPlugin() {
|
||||
}
|
||||
|
||||
public val Context.clock: Clock get() = plugins[ClockManager]?.clock ?: Clock.System
|
||||
|
||||
public val Device.clock: Clock get() = context.clock
|
||||
|
||||
public fun Device.getCoroutineDispatcher(dispatcher: CoroutineDispatcher = Dispatchers.Default): CoroutineDispatcher =
|
||||
context.plugins[ClockManager]?.asDispatcher(dispatcher) ?: dispatcher
|
||||
|
||||
public fun ContextBuilder.withTimeCompression(compression: Double) {
|
||||
require(compression > 0.0) { "Time compression must be greater than zero." }
|
||||
plugin(ClockManager) {
|
||||
"timeCompression" put compression
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.manager.getCoroutineDispatcher
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
@ -15,11 +16,12 @@ public fun <D : Device> D.doRecurring(
|
||||
task: suspend D.() -> Unit,
|
||||
): Job {
|
||||
val taskName = debugTaskName ?: "task[${task.hashCode().toString(16)}]"
|
||||
return launch(CoroutineName(taskName)) {
|
||||
val dispatcher = getCoroutineDispatcher()
|
||||
return launch(CoroutineName(taskName) + dispatcher) {
|
||||
while (isActive) {
|
||||
delay(interval)
|
||||
//launch in parent scope to properly evaluate exceptions
|
||||
this@doRecurring.launch {
|
||||
this@doRecurring.launch(CoroutineName("$taskName-recurring") + dispatcher) {
|
||||
task()
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-jupyter:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-jupyter:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-jupyter:0.3.0")
|
||||
implementation("space.kscience:controls-jupyter:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -12,7 +12,7 @@ Magix service for binding controls devices (both as RPC client and server)
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-magix:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-magix:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -22,6 +22,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-magix:0.3.0")
|
||||
implementation("space.kscience:controls-magix:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -12,7 +12,8 @@ description = """
|
||||
kscience {
|
||||
jvm()
|
||||
js()
|
||||
useCoroutines("1.8.0")
|
||||
native()
|
||||
useCoroutines()
|
||||
useSerialization {
|
||||
json()
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ Automatically checks consistency.
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-modbus:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-modbus:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -24,6 +24,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-modbus:0.3.0")
|
||||
implementation("space.kscience:controls-modbus:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -12,7 +12,7 @@ A client and server connectors for OPC-UA via Eclipse Milo
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-opcua:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-opcua:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -22,6 +22,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-opcua:0.3.0")
|
||||
implementation("space.kscience:controls-opcua:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ Utils to work with controls-kt on Raspberry pi
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-pi:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-pi:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-pi:0.3.0")
|
||||
implementation("space.kscience:controls-pi:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ Implementation of byte ports on top os ktor-io asynchronous API
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-ports-ktor:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-ports-ktor:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-ports-ktor:0.3.0")
|
||||
implementation("space.kscience:controls-ports-ktor:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ Implementation of direct serial port communication with JSerialComm
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-serial:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-serial:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-serial:0.3.0")
|
||||
implementation("space.kscience:controls-serial:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ A combined Magix event loop server with web server for visualization.
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-server:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-server:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-server:0.3.0")
|
||||
implementation("space.kscience:controls-server:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ An API for stand-alone Controls-kt device or a hub.
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-storage:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-storage:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-storage:0.3.0")
|
||||
implementation("space.kscience:controls-storage:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ An implementation of controls-storage on top of JetBrains Xodus.
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-xodus:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-xodus:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-xodus:0.3.0")
|
||||
implementation("space.kscience:controls-xodus:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -2,11 +2,13 @@
|
||||
|
||||
Dashboard and visualization extensions for devices
|
||||
|
||||
Hello world!
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-vision:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:controls-vision:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +18,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-vision:0.3.0")
|
||||
implementation("space.kscience:controls-vision:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -10,6 +10,7 @@ description = """
|
||||
kscience {
|
||||
fullStack("js/controls-vision.js")
|
||||
useKtor()
|
||||
useSerialization()
|
||||
useContextReceivers()
|
||||
dependencies {
|
||||
api(projects.controlsCore)
|
||||
|
15
controls-vision/docs/README-TEMPLATE.md
Normal file
15
controls-vision/docs/README-TEMPLATE.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Module ${name}
|
||||
|
||||
${description}
|
||||
|
||||
<#if features?has_content>
|
||||
## Features
|
||||
|
||||
${features}
|
||||
|
||||
</#if>
|
||||
<#if published>
|
||||
## Usage
|
||||
|
||||
${artifact}
|
||||
</#if>
|
@ -0,0 +1,24 @@
|
||||
package space.kscience.controls.vision
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.boolean
|
||||
import space.kscience.visionforge.AbstractVision
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.html.VisionOfHtml
|
||||
|
||||
/**
|
||||
* A [Vision] that shows an indicator
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("controls.indicator")
|
||||
public class BooleanIndicatorVision : AbstractVision(), VisionOfHtml {
|
||||
public val isOn: Boolean by properties.boolean(false)
|
||||
}
|
||||
|
||||
///**
|
||||
// * A [Vision] that allows both showing the value and changing it
|
||||
// */
|
||||
//public interface RegulatorVision: IndicatorVision{
|
||||
//
|
||||
//}
|
@ -6,7 +6,6 @@ import kotlinx.serialization.modules.subclass
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
import space.kscience.visionforge.plotly.VisionOfPlotly
|
||||
|
||||
public expect class ControlVisionPlugin: VisionPlugin{
|
||||
public companion object: PluginFactory<ControlVisionPlugin>
|
||||
@ -14,6 +13,6 @@ public expect class ControlVisionPlugin: VisionPlugin{
|
||||
|
||||
internal val controlsVisionSerializersModule = SerializersModule {
|
||||
polymorphic(Vision::class) {
|
||||
subclass(VisionOfPlotly.serializer())
|
||||
subclass(BooleanIndicatorVision.serializer())
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package space.kscience.controls.vision
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.node
|
||||
import space.kscience.visionforge.AbstractVision
|
||||
import space.kscience.visionforge.Vision
|
||||
|
||||
/**
|
||||
* A [Vision] that shows an indicator
|
||||
*/
|
||||
public class IndicatorVision: AbstractVision() {
|
||||
public val value: Meta? by properties.node()
|
||||
}
|
||||
|
||||
///**
|
||||
// * A [Vision] that allows both showing the value and changing it
|
||||
// */
|
||||
//public interface RegulatorVision: IndicatorVision{
|
||||
//
|
||||
//}
|
@ -1,4 +1,4 @@
|
||||
@file:OptIn(FlowPreview::class)
|
||||
@file:OptIn(FlowPreview::class, FlowPreview::class)
|
||||
|
||||
package space.kscience.controls.vision
|
||||
|
||||
|
@ -5,13 +5,29 @@ import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
import space.kscience.visionforge.html.ElementVisionRenderer
|
||||
|
||||
private val indicatorRenderer = ElementVisionRenderer<BooleanIndicatorVision> { name, vision: BooleanIndicatorVision, meta ->
|
||||
|
||||
}
|
||||
|
||||
|
||||
public actual class ControlVisionPlugin : VisionPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override val visionSerializersModule: SerializersModule get() = controlsVisionSerializersModule
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = when (target) {
|
||||
ElementVisionRenderer.TYPE -> mapOf(
|
||||
"indicator".asName() to indicatorRenderer
|
||||
)
|
||||
|
||||
else -> super.content(target)
|
||||
}
|
||||
|
||||
public actual companion object : PluginFactory<ControlVisionPlugin> {
|
||||
override val tag: PluginTag = PluginTag("controls.vision")
|
||||
|
||||
|
@ -13,6 +13,8 @@ import space.kscience.plotly.PlotlyConfig
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import space.kscience.visionforge.html.VisionTagConsumer
|
||||
import space.kscience.visionforge.markup.MarkupPlugin
|
||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||
import space.kscience.visionforge.plotly.plotly
|
||||
import space.kscience.visionforge.server.VisionRoute
|
||||
import space.kscience.visionforge.server.close
|
||||
@ -25,29 +27,38 @@ public fun Context.showDashboard(
|
||||
routes: Routing.() -> Unit = {},
|
||||
configurationBuilder: VisionRoute.() -> Unit = {},
|
||||
visionFragment: HtmlVisionFragment,
|
||||
): ApplicationEngine = embeddedServer(CIO, port = port) {
|
||||
routing {
|
||||
staticResources("", null, null)
|
||||
routes()
|
||||
): ApplicationEngine {
|
||||
//create a sub-context for visualization
|
||||
val visualisationContext = buildContext {
|
||||
plugin(PlotlyPlugin)
|
||||
plugin(ControlVisionPlugin)
|
||||
plugin(MarkupPlugin)
|
||||
}
|
||||
|
||||
visionPage(
|
||||
visionManager,
|
||||
VisionPage.scriptHeader("js/controls-vision.js"),
|
||||
configurationBuilder = configurationBuilder,
|
||||
visionFragment = visionFragment
|
||||
)
|
||||
}.also {
|
||||
it.start(false)
|
||||
it.openInBrowser()
|
||||
return visualisationContext.embeddedServer(CIO, port = port) {
|
||||
routing {
|
||||
staticResources("", null, null)
|
||||
routes()
|
||||
}
|
||||
|
||||
visionPage(
|
||||
visualisationContext.visionManager,
|
||||
VisionPage.scriptHeader("js/controls-vision.js"),
|
||||
configurationBuilder = configurationBuilder,
|
||||
visionFragment = visionFragment
|
||||
)
|
||||
}.also {
|
||||
it.start(false)
|
||||
it.openInBrowser()
|
||||
|
||||
|
||||
println("Enter 'exit' to close server")
|
||||
while (readlnOrNull() != "exit") {
|
||||
//
|
||||
println("Enter 'exit' to close server")
|
||||
while (readlnOrNull() != "exit") {
|
||||
//
|
||||
}
|
||||
|
||||
it.close()
|
||||
}
|
||||
|
||||
it.close()
|
||||
}
|
||||
|
||||
context(VisionTagConsumer<*>)
|
||||
|
@ -14,8 +14,10 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.controls.constructor.*
|
||||
import space.kscience.controls.constructor.DeviceConstructor
|
||||
import space.kscience.controls.constructor.DoubleInRangeState
|
||||
import space.kscience.controls.constructor.device
|
||||
import space.kscience.controls.constructor.deviceProperty
|
||||
import space.kscience.controls.constructor.library.*
|
||||
import space.kscience.controls.manager.ClockManager
|
||||
import space.kscience.controls.manager.DeviceManager
|
||||
@ -40,77 +42,41 @@ import kotlin.time.DurationUnit
|
||||
|
||||
|
||||
class LinearDrive(
|
||||
drive: Drive,
|
||||
start: LimitSwitch,
|
||||
end: LimitSwitch,
|
||||
pidParameters: PidParameters,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : DeviceConstructor(drive.context, meta) {
|
||||
|
||||
val drive: Drive by device(drive)
|
||||
val pid by device(PidRegulator(drive, pidParameters))
|
||||
|
||||
val start by device(start)
|
||||
val end by device(end)
|
||||
|
||||
val position by deviceProperty(drive, Drive.position, Double.NaN)
|
||||
|
||||
val target by deviceProperty(pid, Regulator.target, 0.0)
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut to create a virtual [LimitSwitch] from [DoubleInRangeState]
|
||||
*/
|
||||
fun LinearDrive(
|
||||
context: Context,
|
||||
state: DoubleRangeState,
|
||||
positionState: DoubleInRangeState,
|
||||
mass: Double,
|
||||
pidParameters: PidParameters,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : DeviceConstructor(context, meta) {
|
||||
): LinearDrive = LinearDrive(
|
||||
drive = VirtualDrive(context, mass, positionState),
|
||||
start = VirtualLimitSwitch(context, positionState.atStart),
|
||||
end = VirtualLimitSwitch(context, positionState.atEnd),
|
||||
pidParameters = pidParameters,
|
||||
meta = meta
|
||||
)
|
||||
|
||||
val drive by device(VirtualDrive.factory(mass, state))
|
||||
val pid by device(PidRegulator(drive, pidParameters))
|
||||
|
||||
val start by device(LimitSwitch(state.atStartState))
|
||||
val end by device(LimitSwitch(state.atEndState))
|
||||
|
||||
|
||||
val positionState: DoubleRangeState by property(state)
|
||||
|
||||
private val targetState: MutableDeviceState<Double> by deviceProperty(pid, Regulator.target, 0.0)
|
||||
var target: Double by targetState
|
||||
}
|
||||
|
||||
|
||||
private fun Context.launchPidDevice(
|
||||
state: DoubleRangeState,
|
||||
pidParameters: PidParameters,
|
||||
mass: Double,
|
||||
) = launch {
|
||||
val device = install(
|
||||
"device",
|
||||
LinearDrive(this@launchPidDevice, state, mass, pidParameters)
|
||||
).apply {
|
||||
val clock = context.clock
|
||||
val clockStart = clock.now()
|
||||
doRecurring(10.milliseconds) {
|
||||
val timeFromStart = clock.now() - clockStart
|
||||
val t = timeFromStart.toDouble(DurationUnit.SECONDS)
|
||||
val freq = 0.1
|
||||
target = 5 * sin(2.0 * PI * freq * t) +
|
||||
sin(2 * PI * 21 * freq * t + 0.02 * (timeFromStart / pidParameters.timeStep))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val maxAge = 10.seconds
|
||||
|
||||
showDashboard {
|
||||
plot {
|
||||
plotNumberState(context, state, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "real position"
|
||||
}
|
||||
plotDeviceProperty(device.pid, Regulator.position.name, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "read position"
|
||||
}
|
||||
|
||||
plotDeviceProperty(device.pid, Regulator.target.name, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "target"
|
||||
}
|
||||
}
|
||||
|
||||
plot {
|
||||
plotDeviceProperty(device.start, LimitSwitch.locked.name, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "start measured"
|
||||
mode = ScatterMode.markers
|
||||
}
|
||||
plotDeviceProperty(device.end, LimitSwitch.locked.name, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "end measured"
|
||||
mode = ScatterMode.markers
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun main() = application {
|
||||
val context = Context {
|
||||
@ -140,12 +106,57 @@ fun main() = application {
|
||||
)
|
||||
}
|
||||
|
||||
context.launchPidDevice(
|
||||
DoubleRangeState(0.0, -6.0..6.0),
|
||||
pidParameters,
|
||||
mass = 0.05
|
||||
val state = DoubleInRangeState(0.0, -6.0..6.0)
|
||||
|
||||
val linearDrive = context.install(
|
||||
"linearDrive",
|
||||
LinearDrive(context, state, 0.05, pidParameters)
|
||||
)
|
||||
|
||||
val clockStart = context.clock.now()
|
||||
linearDrive.doRecurring(10.milliseconds) {
|
||||
val timeFromStart = clock.now() - clockStart
|
||||
val t = timeFromStart.toDouble(DurationUnit.SECONDS)
|
||||
val freq = 0.1
|
||||
target.value = 5 * sin(2.0 * PI * freq * t) +
|
||||
sin(2 * PI * 21 * freq * t + 0.02 * (timeFromStart / pidParameters.timeStep))
|
||||
}
|
||||
|
||||
|
||||
val maxAge = 10.seconds
|
||||
|
||||
context.showDashboard {
|
||||
plot {
|
||||
plotNumberState(context, state, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "real position"
|
||||
}
|
||||
plotDeviceProperty(linearDrive.pid, Regulator.position.name, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "read position"
|
||||
}
|
||||
|
||||
plotDeviceProperty(linearDrive.pid, Regulator.target.name, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "target"
|
||||
}
|
||||
}
|
||||
|
||||
plot {
|
||||
plotDeviceProperty(
|
||||
linearDrive.start,
|
||||
LimitSwitch.locked.name,
|
||||
maxAge = maxAge,
|
||||
sampling = 50.milliseconds
|
||||
) {
|
||||
name = "start measured"
|
||||
mode = ScatterMode.markers
|
||||
}
|
||||
plotDeviceProperty(linearDrive.end, LimitSwitch.locked.name, maxAge = maxAge, sampling = 50.milliseconds) {
|
||||
name = "end measured"
|
||||
mode = ScatterMode.markers
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Window(title = "Pid regulator simulator", onCloseRequest = ::exitApplication) {
|
||||
MaterialTheme {
|
||||
Column {
|
@ -97,7 +97,7 @@ fun AxisPane(axes: Map<String, PiMotionMasterDevice.Axis>) {
|
||||
@Composable
|
||||
fun PiMotionMasterApp(device: PiMotionMasterDevice) {
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
// val scope = rememberCoroutineScope()
|
||||
val connected by device.composeState(PiMotionMasterDevice.connected, false)
|
||||
var debugServerJob by remember { mutableStateOf<Job?>(null) }
|
||||
var axes by remember { mutableStateOf<Map<String, PiMotionMasterDevice.Axis>?>(null) }
|
||||
|
30
docs/templates/ARTIFACT-TEMPLATE.md
vendored
30
docs/templates/ARTIFACT-TEMPLATE.md
vendored
@ -1,30 +0,0 @@
|
||||
## 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}")
|
||||
}
|
||||
```
|
@ -6,7 +6,7 @@ A kotlin API for magix standard and some zero-dependency magix services
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-api:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-api:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-api:0.3.0")
|
||||
implementation("space.kscience:magix-api:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ Java API to work with magix endpoints without Kotlin
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-java-endpoint:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-java-endpoint:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-java-endpoint:0.3.0")
|
||||
implementation("space.kscience:magix-java-endpoint:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ MQTT client magix endpoint
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-mqtt:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-mqtt:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-mqtt:0.3.0")
|
||||
implementation("space.kscience:magix-mqtt:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ RabbitMQ client magix endpoint
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-rabbit:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-rabbit:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-rabbit:0.3.0")
|
||||
implementation("space.kscience:magix-rabbit:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ Magix endpoint (client) based on RSocket
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-rsocket:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-rsocket:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-rsocket:0.3.0")
|
||||
implementation("space.kscience:magix-rsocket:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ A magix event loop implementation in Kotlin. Includes HTTP/SSE and RSocket route
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-server:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-server:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-server:0.3.0")
|
||||
implementation("space.kscience:magix-server:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ Magix history database API
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-storage:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-storage:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-storage:0.3.0")
|
||||
implementation("space.kscience:magix-storage:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-storage-xodus:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-storage-xodus:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-storage-xodus:0.3.0")
|
||||
implementation("space.kscience:magix-storage-xodus:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ ZMQ client endpoint for Magix
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:magix-zmq:0.3.0`.
|
||||
The Maven coordinates of this project are `space.kscience:magix-zmq:0.4.0-dev-1`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
@ -16,6 +16,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:magix-zmq:0.3.0")
|
||||
implementation("space.kscience:magix-zmq:0.4.0-dev-1")
|
||||
}
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user