Refactor car demo. Add meta property.
This commit is contained in:
parent
c1a1b6e696
commit
4dc33c012d
@ -21,6 +21,12 @@ import space.kscience.dataforge.names.Name
|
|||||||
*/
|
*/
|
||||||
@Type(DEVICE_TARGET)
|
@Type(DEVICE_TARGET)
|
||||||
public interface Device : Closeable, ContextAware, CoroutineScope {
|
public interface Device : Closeable, ContextAware, CoroutineScope {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial configuration meta for the device
|
||||||
|
*/
|
||||||
|
public val meta: Meta get() = Meta.EMPTY
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of supported property descriptors
|
* List of supported property descriptors
|
||||||
*/
|
*/
|
||||||
|
@ -17,7 +17,7 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public abstract class DeviceBase<D : DeviceBase<D>>(
|
public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||||
override val context: Context = Global,
|
override val context: Context = Global,
|
||||||
public val meta: Meta = Meta.EMPTY
|
override val meta: Meta = Meta.EMPTY
|
||||||
) : Device {
|
) : Device {
|
||||||
|
|
||||||
public abstract val properties: Map<String, DevicePropertySpec<D, *>> //get() = spec.properties
|
public abstract val properties: Map<String, DevicePropertySpec<D, *>> //get() = spec.properties
|
||||||
@ -130,7 +130,7 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
|||||||
* @param D recursive self-type for properties and actions
|
* @param D recursive self-type for properties and actions
|
||||||
*/
|
*/
|
||||||
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||||
public val spec: DeviceSpec<D>,
|
public val spec: DeviceSpec<in D>,
|
||||||
context: Context = Global,
|
context: Context = Global,
|
||||||
meta: Meta = Meta.EMPTY
|
meta: Meta = Meta.EMPTY
|
||||||
) : DeviceBase<D>(context, meta) {
|
) : DeviceBase<D>(context, meta) {
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package ru.mipt.npm.controls.spec
|
||||||
|
|
||||||
|
import ru.mipt.npm.controls.api.Device
|
||||||
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
|
||||||
|
internal object DeviceMetaPropertySpec: DevicePropertySpec<Device,Meta> {
|
||||||
|
override val descriptor: PropertyDescriptor = PropertyDescriptor("@meta")
|
||||||
|
|
||||||
|
override val converter: MetaConverter<Meta> = MetaConverter.meta
|
||||||
|
|
||||||
|
@InternalDeviceAPI
|
||||||
|
override suspend fun read(device: Device): Meta = device.meta
|
||||||
|
}
|
@ -17,15 +17,10 @@ import space.kscience.dataforge.meta.transformations.MetaConverter
|
|||||||
/**
|
/**
|
||||||
* This API is internal and should not be used in user code
|
* This API is internal and should not be used in user code
|
||||||
*/
|
*/
|
||||||
@RequiresOptIn
|
@RequiresOptIn("This API should not be called outside of Device internals")
|
||||||
public annotation class InternalDeviceAPI
|
public annotation class InternalDeviceAPI
|
||||||
|
|
||||||
public interface DevicePropertySpec<in D : Device, T> {
|
public interface DevicePropertySpec<in D : Device, T> {
|
||||||
/**
|
|
||||||
* Property name, should be unique in device
|
|
||||||
*/
|
|
||||||
public val name: String
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property descriptor
|
* Property descriptor
|
||||||
*/
|
*/
|
||||||
@ -43,6 +38,11 @@ public interface DevicePropertySpec<in D : Device, T> {
|
|||||||
public suspend fun read(device: D): T
|
public suspend fun read(device: D): T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property name, should be unique in device
|
||||||
|
*/
|
||||||
|
public val DevicePropertySpec<*, *>.name: String get() = descriptor.name
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta =
|
public suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta =
|
||||||
converter.objectToMeta(read(device))
|
converter.objectToMeta(read(device))
|
||||||
@ -62,11 +62,6 @@ public suspend fun <D : Device, T> WritableDevicePropertySpec<D, T>.writeMeta(de
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface DeviceActionSpec<in D : Device, I, O> {
|
public interface DeviceActionSpec<in D : Device, I, O> {
|
||||||
/**
|
|
||||||
* Action name, should be unique in device
|
|
||||||
*/
|
|
||||||
public val name: String
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action descriptor
|
* Action descriptor
|
||||||
*/
|
*/
|
||||||
@ -82,9 +77,14 @@ public interface DeviceActionSpec<in D : Device, I, O> {
|
|||||||
public suspend fun execute(device: D, input: I?): O?
|
public suspend fun execute(device: D, input: I?): O?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action name, should be unique in device
|
||||||
|
*/
|
||||||
|
public val DeviceActionSpec<*, *, *>.name: String get() = descriptor.name
|
||||||
|
|
||||||
public suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
|
public suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
|
||||||
device: D,
|
device: D,
|
||||||
item: Meta?
|
item: Meta?,
|
||||||
): Meta? {
|
): Meta? {
|
||||||
val arg = item?.let { inputConverter.metaToObject(item) }
|
val arg = item?.let { inputConverter.metaToObject(item) }
|
||||||
val res = execute(device, arg)
|
val res = execute(device, arg)
|
||||||
@ -93,24 +93,24 @@ public suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
|
|||||||
|
|
||||||
|
|
||||||
public suspend fun <D : DeviceBase<D>, T : Any> D.read(
|
public suspend fun <D : DeviceBase<D>, T : Any> D.read(
|
||||||
propertySpec: DevicePropertySpec<D, T>
|
propertySpec: DevicePropertySpec<D, T>,
|
||||||
): T = propertySpec.read()
|
): T = propertySpec.read()
|
||||||
|
|
||||||
public suspend fun <D : Device, T : Any> D.read(
|
public suspend fun <D : Device, T : Any> D.read(
|
||||||
propertySpec: DevicePropertySpec<D, T>
|
propertySpec: DevicePropertySpec<D, T>,
|
||||||
): T = propertySpec.converter.metaToObject(readProperty(propertySpec.name))
|
): T = propertySpec.converter.metaToObject(readProperty(propertySpec.name))
|
||||||
?: error("Property meta converter returned null")
|
?: error("Property meta converter returned null")
|
||||||
|
|
||||||
public fun <D : Device, T> D.write(
|
public fun <D : Device, T> D.write(
|
||||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||||
value: T
|
value: T,
|
||||||
): Job = launch {
|
): Job = launch {
|
||||||
writeProperty(propertySpec.name, propertySpec.converter.objectToMeta(value))
|
writeProperty(propertySpec.name, propertySpec.converter.objectToMeta(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>, T> D.write(
|
public fun <D : DeviceBase<D>, T> D.write(
|
||||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||||
value: T
|
value: T,
|
||||||
): Job = launch {
|
): Job = launch {
|
||||||
propertySpec.write(value)
|
propertySpec.write(value)
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ public fun <D : DeviceBase<D>, T> D.write(
|
|||||||
*/
|
*/
|
||||||
public fun <D : Device, T> Device.onPropertyChange(
|
public fun <D : Device, T> Device.onPropertyChange(
|
||||||
spec: DevicePropertySpec<D, T>,
|
spec: DevicePropertySpec<D, T>,
|
||||||
callback: suspend PropertyChangedMessage.(T?) -> Unit
|
callback: suspend PropertyChangedMessage.(T?) -> Unit,
|
||||||
): Job = messageFlow
|
): Job = messageFlow
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
.filterIsInstance<PropertyChangedMessage>()
|
||||||
.filter { it.property == spec.name }
|
.filter { it.property == spec.name }
|
||||||
|
@ -14,7 +14,10 @@ import kotlin.reflect.KProperty1
|
|||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public abstract class DeviceSpec<D : Device> {
|
public abstract class DeviceSpec<D : Device> {
|
||||||
private val _properties = HashMap<String, DevicePropertySpec<D, *>>()
|
//initializing meta property for everyone
|
||||||
|
private val _properties = hashMapOf<String, DevicePropertySpec<D, *>>(
|
||||||
|
DeviceMetaPropertySpec.name to DeviceMetaPropertySpec
|
||||||
|
)
|
||||||
public val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
|
public val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
|
||||||
|
|
||||||
private val _actions = HashMap<String, DeviceActionSpec<D, *, *>>()
|
private val _actions = HashMap<String, DeviceActionSpec<D, *, *>>()
|
||||||
@ -31,8 +34,7 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
): DevicePropertySpec<D, T> {
|
): DevicePropertySpec<D, T> {
|
||||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||||
override val name: String = readOnlyProperty.name
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(readOnlyProperty.name).apply(descriptorBuilder)
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
override suspend fun read(device: D): T =
|
override suspend fun read(device: D): T =
|
||||||
withContext(device.coroutineContext) { readOnlyProperty.get(device) }
|
withContext(device.coroutineContext) { readOnlyProperty.get(device) }
|
||||||
@ -41,15 +43,38 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> property(
|
public fun <T : Any> property(
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
readOnlyProperty: KProperty1<D, T>,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?,DevicePropertySpec<D, T>>> =
|
||||||
|
PropertyDelegateProvider { _, property ->
|
||||||
|
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||||
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
||||||
|
//TODO add type from converter
|
||||||
|
writable = true
|
||||||
|
}.apply(descriptorBuilder)
|
||||||
|
|
||||||
|
override val converter: MetaConverter<T> = converter
|
||||||
|
|
||||||
|
override suspend fun read(device: D): T = withContext(device.coroutineContext) {
|
||||||
|
readOnlyProperty.get(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerProperty(deviceProperty)
|
||||||
|
ReadOnlyProperty { _, _ ->
|
||||||
|
deviceProperty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T : Any> mutableProperty(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
readWriteProperty: KMutableProperty1<D, T>,
|
readWriteProperty: KMutableProperty1<D, T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
||||||
PropertyDelegateProvider { _, property ->
|
PropertyDelegateProvider { _, property ->
|
||||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||||
override val name: String = property.name
|
|
||||||
|
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(name).apply {
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
||||||
//TODO add type from converter
|
//TODO add type from converter
|
||||||
writable = true
|
writable = true
|
||||||
}.apply(descriptorBuilder)
|
}.apply(descriptorBuilder)
|
||||||
@ -79,8 +104,7 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||||
val propertyName = name ?: property.name
|
val propertyName = name ?: property.name
|
||||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||||
override val name: String = propertyName
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
|
|
||||||
override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() }
|
override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() }
|
||||||
@ -91,7 +115,7 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> property(
|
public fun <T : Any> mutableProperty(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
@ -101,8 +125,7 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
||||||
val propertyName = name ?: property.name
|
val propertyName = name ?: property.name
|
||||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||||
override val name: String = propertyName
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
|
|
||||||
override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() }
|
override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() }
|
||||||
@ -133,7 +156,6 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||||
val actionName = name ?: property.name
|
val actionName = name ?: property.name
|
||||||
val deviceAction = object : DeviceActionSpec<D, I, O> {
|
val deviceAction = object : DeviceActionSpec<D, I, O> {
|
||||||
override val name: String = actionName
|
|
||||||
override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply(descriptorBuilder)
|
override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply(descriptorBuilder)
|
||||||
|
|
||||||
override val inputConverter: MetaConverter<I> = inputConverter
|
override val inputConverter: MetaConverter<I> = inputConverter
|
||||||
|
@ -97,7 +97,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
|||||||
read: suspend D.() -> Boolean,
|
read: suspend D.() -> Boolean,
|
||||||
write: suspend D.(Boolean) -> Unit
|
write: suspend D.(Boolean) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
||||||
property(
|
mutableProperty(
|
||||||
MetaConverter.boolean,
|
MetaConverter.boolean,
|
||||||
{
|
{
|
||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
@ -117,7 +117,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
|||||||
read: suspend D.() -> Number,
|
read: suspend D.() -> Number,
|
||||||
write: suspend D.(Number) -> Unit
|
write: suspend D.(Number) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Number>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Number>>> =
|
||||||
property(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
|
mutableProperty(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
@ -125,7 +125,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
|||||||
read: suspend D.() -> Double,
|
read: suspend D.() -> Double,
|
||||||
write: suspend D.(Double) -> Unit
|
write: suspend D.(Double) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Double>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Double>>> =
|
||||||
property(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
|
mutableProperty(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
@ -133,7 +133,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
|||||||
read: suspend D.() -> String,
|
read: suspend D.() -> String,
|
||||||
write: suspend D.(String) -> Unit
|
write: suspend D.(String) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
||||||
property(MetaConverter.string, descriptorBuilder, name, read, write)
|
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
@ -141,4 +141,4 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
|||||||
read: suspend D.() -> Meta,
|
read: suspend D.() -> Meta,
|
||||||
write: suspend D.(Meta) -> Unit
|
write: suspend D.(Meta) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Meta>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Meta>>> =
|
||||||
property(MetaConverter.meta, descriptorBuilder, name, read, write)
|
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)
|
40
demo/car/build.gradle.kts
Normal file
40
demo/car/build.gradle.kts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
id("org.openjfx.javafxplugin")
|
||||||
|
application
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://repo.kotlin.link")
|
||||||
|
}
|
||||||
|
|
||||||
|
val ktorVersion: String by rootProject.extra
|
||||||
|
val rsocketVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.controlsCore)
|
||||||
|
implementation(projects.magix.magixApi)
|
||||||
|
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.3.1")
|
||||||
|
implementation("no.tornado:tornadofx:1.7.20")
|
||||||
|
implementation("space.kscience:plotlykt-server:0.5.0-dev-1")
|
||||||
|
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
freeCompilerArgs = freeCompilerArgs + listOf("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
javafx {
|
||||||
|
version = "14"
|
||||||
|
modules("javafx.controls")
|
||||||
|
}
|
||||||
|
|
||||||
|
//application {
|
||||||
|
// mainClass.set("ru.mipt.npm.controls.demo.DemoControllerViewKt")
|
||||||
|
//}
|
@ -0,0 +1,137 @@
|
|||||||
|
@file:OptIn(ExperimentalTime::class)
|
||||||
|
|
||||||
|
package ru.mipt.npm.controls.demo.car
|
||||||
|
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import ru.mipt.npm.controls.api.Device
|
||||||
|
import ru.mipt.npm.controls.spec.DeviceBySpec
|
||||||
|
import ru.mipt.npm.controls.spec.DeviceSpec
|
||||||
|
import ru.mipt.npm.controls.spec.doRecurring
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
import space.kscience.dataforge.context.Factory
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.MetaRepr
|
||||||
|
import space.kscience.dataforge.meta.double
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
|
data class Vector2D(var x: Double = 0.0, var y: Double = 0.0) : MetaRepr {
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = objectToMeta(this)
|
||||||
|
|
||||||
|
operator fun div(arg: Double): Vector2D = Vector2D(x / arg, y / arg)
|
||||||
|
|
||||||
|
companion object CoordinatesMetaConverter : MetaConverter<Vector2D> {
|
||||||
|
override fun metaToObject(meta: Meta): Vector2D = Vector2D(
|
||||||
|
meta["x"].double ?: 0.0,
|
||||||
|
meta["y"].double ?: 0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun objectToMeta(obj: Vector2D): Meta = Meta {
|
||||||
|
"x" put obj.x
|
||||||
|
"y" put obj.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IVirtualCar : Device {
|
||||||
|
var speedState: Vector2D
|
||||||
|
var locationState: Vector2D
|
||||||
|
var accelerationState: Vector2D
|
||||||
|
}
|
||||||
|
|
||||||
|
class VirtualCar(context: Context, meta: Meta) : DeviceBySpec<VirtualCar>(VirtualCar, context, meta), IVirtualCar {
|
||||||
|
private val timeScale = 1e-3
|
||||||
|
|
||||||
|
private val mass by meta.double(1000.0) // mass in kilograms
|
||||||
|
|
||||||
|
override var speedState: Vector2D = Vector2D()
|
||||||
|
|
||||||
|
override var locationState: Vector2D = Vector2D()
|
||||||
|
|
||||||
|
override var accelerationState: Vector2D = Vector2D()
|
||||||
|
set(value) {
|
||||||
|
update()
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timeState: Instant? = null
|
||||||
|
|
||||||
|
private fun update(newTime: Instant = Clock.System.now()) {
|
||||||
|
//initialize time if it is not initialized
|
||||||
|
if (timeState == null) {
|
||||||
|
timeState = newTime
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val dt: Double = (newTime - (timeState ?: return)).inWholeMilliseconds.toDouble() * timeScale
|
||||||
|
|
||||||
|
locationState.apply {
|
||||||
|
x += speedState.x * dt + accelerationState.x * dt.pow(2) / 2.0
|
||||||
|
y += speedState.y * dt + accelerationState.y * dt.pow(2) / 2.0
|
||||||
|
}
|
||||||
|
|
||||||
|
speedState.apply {
|
||||||
|
x += dt * accelerationState.x
|
||||||
|
y += dt * accelerationState.y
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO apply friction. One can introduce rotation of the cabin and different friction coefficients along the axis
|
||||||
|
launch {
|
||||||
|
//update logical states
|
||||||
|
location.read()
|
||||||
|
speed.read()
|
||||||
|
acceleration.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun applyForce(force: Vector2D, duration: Duration) {
|
||||||
|
launch {
|
||||||
|
update()
|
||||||
|
accelerationState = force / mass
|
||||||
|
delay(duration)
|
||||||
|
accelerationState.apply {
|
||||||
|
x = 0.0
|
||||||
|
y = 0.0
|
||||||
|
}
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
|
override suspend fun open() {
|
||||||
|
super<DeviceBySpec>.open()
|
||||||
|
//initializing the clock
|
||||||
|
timeState = Clock.System.now()
|
||||||
|
//starting regular updates
|
||||||
|
doRecurring(Duration.milliseconds(100)) {
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : DeviceSpec<IVirtualCar>(), Factory<VirtualCar> {
|
||||||
|
override fun invoke(meta: Meta, context: Context): VirtualCar = VirtualCar(context, meta)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-only speed
|
||||||
|
*/
|
||||||
|
val speed by property(Vector2D, IVirtualCar::speedState)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-only location
|
||||||
|
*/
|
||||||
|
val location by property(Vector2D, IVirtualCar::locationState)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* writable acceleration
|
||||||
|
*/
|
||||||
|
val acceleration by mutableProperty(Vector2D, IVirtualCar::accelerationState)
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,20 @@
|
|||||||
package ru.mipt.npm.controls.demo.virtual_car
|
package ru.mipt.npm.controls.demo.car
|
||||||
|
|
||||||
import io.ktor.server.engine.ApplicationEngine
|
|
||||||
import javafx.beans.property.DoubleProperty
|
import javafx.beans.property.DoubleProperty
|
||||||
import javafx.scene.Parent
|
import javafx.scene.Parent
|
||||||
import javafx.scene.control.TextField
|
import javafx.scene.control.TextField
|
||||||
import javafx.scene.layout.Priority
|
import javafx.scene.layout.Priority
|
||||||
import javafx.stage.Stage
|
import javafx.stage.Stage
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.controls.api.DeviceMessage
|
|
||||||
import ru.mipt.npm.controls.client.connectToMagix
|
|
||||||
import ru.mipt.npm.controls.controllers.DeviceManager
|
import ru.mipt.npm.controls.controllers.DeviceManager
|
||||||
import ru.mipt.npm.controls.controllers.install
|
import ru.mipt.npm.controls.controllers.install
|
||||||
import ru.mipt.npm.controls.demo.virtual_car.VirtualCar.Companion.acceleration
|
import ru.mipt.npm.controls.demo.car.VirtualCar.Companion.acceleration
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
|
||||||
import ru.mipt.npm.magix.rsocket.rSocketWithTcp
|
|
||||||
import ru.mipt.npm.magix.server.startMagixServer
|
|
||||||
import space.kscience.dataforge.context.*
|
import space.kscience.dataforge.context.*
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
class VirtualCarController : Controller(), ContextAware {
|
class VirtualCarController : Controller(), ContextAware {
|
||||||
|
|
||||||
var device: VirtualCar? = null
|
var device: VirtualCar? = null
|
||||||
var magixServer: ApplicationEngine? = null
|
|
||||||
|
|
||||||
override val context = Context("demoDevice") {
|
override val context = Context("demoDevice") {
|
||||||
plugin(DeviceManager)
|
plugin(DeviceManager)
|
||||||
@ -32,18 +25,11 @@ class VirtualCarController : Controller(), ContextAware {
|
|||||||
fun init() {
|
fun init() {
|
||||||
context.launch {
|
context.launch {
|
||||||
device = deviceManager.install("virtual-car", VirtualCar)
|
device = deviceManager.install("virtual-car", VirtualCar)
|
||||||
//starting magix event loop
|
|
||||||
magixServer = startMagixServer(enableRawRSocket = true, enableZmq = true)
|
|
||||||
//Launch device client and connect it to the server
|
|
||||||
val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost", DeviceMessage.serializer())
|
|
||||||
deviceManager.connectToMagix(deviceEndpoint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shutdown() {
|
fun shutdown() {
|
||||||
logger.info { "Shutting down..." }
|
logger.info { "Shutting down..." }
|
||||||
magixServer?.stop(1000, 5000)
|
|
||||||
logger.info { "Magix server stopped" }
|
|
||||||
device?.close()
|
device?.close()
|
||||||
logger.info { "Device server stopped" }
|
logger.info { "Device server stopped" }
|
||||||
context.close()
|
context.close()
|
||||||
@ -80,7 +66,8 @@ class VirtualCarControllerView : View(title = " Virtual car controller remote")
|
|||||||
action {
|
action {
|
||||||
controller.device?.run {
|
controller.device?.run {
|
||||||
launch {
|
launch {
|
||||||
acceleration.write(Coordinates(accelerationXProperty.get(), accelerationYProperty.get()))
|
acceleration.write(Vector2D(accelerationXProperty.get(),
|
||||||
|
accelerationYProperty.get()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -38,15 +38,15 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<DemoDevice>(DemoDe
|
|||||||
override fun invoke(meta: Meta, context: Context): DemoDevice = DemoDevice(context, meta)
|
override fun invoke(meta: Meta, context: Context): DemoDevice = DemoDevice(context, meta)
|
||||||
|
|
||||||
// register virtual properties based on actual object state
|
// register virtual properties based on actual object state
|
||||||
val timeScale by property(MetaConverter.double, DemoDevice::timeScaleState) {
|
val timeScale by mutableProperty(MetaConverter.double, DemoDevice::timeScaleState) {
|
||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
type(ValueType.NUMBER)
|
type(ValueType.NUMBER)
|
||||||
}
|
}
|
||||||
info = "Real to virtual time scale"
|
info = "Real to virtual time scale"
|
||||||
}
|
}
|
||||||
|
|
||||||
val sinScale by property(MetaConverter.double, DemoDevice::sinScaleState)
|
val sinScale by mutableProperty(MetaConverter.double, DemoDevice::sinScaleState)
|
||||||
val cosScale by property(MetaConverter.double, DemoDevice::cosScaleState)
|
val cosScale by mutableProperty(MetaConverter.double, DemoDevice::cosScaleState)
|
||||||
|
|
||||||
val sin by doubleProperty {
|
val sin by doubleProperty {
|
||||||
val time = Instant.now()
|
val time = Instant.now()
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.demo.virtual_car
|
|
||||||
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import ru.mipt.npm.controls.spec.*
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.context.Factory
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.double
|
|
||||||
import space.kscience.dataforge.meta.get
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
import java.time.Instant
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.ExperimentalTime
|
|
||||||
|
|
||||||
data class Coordinates(val x: Double = 0.0, val y: Double = 0.0)
|
|
||||||
|
|
||||||
class VirtualCar(context: Context, meta: Meta) : DeviceBySpec<VirtualCar>(VirtualCar, context, meta) {
|
|
||||||
private var speedState: Coordinates = Coordinates()
|
|
||||||
|
|
||||||
private var locationState: Coordinates = Coordinates()
|
|
||||||
|
|
||||||
private var accelerationState: Coordinates = Coordinates()
|
|
||||||
set(value) {
|
|
||||||
updateSpeedLocationTime()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
private var timeState = Instant.now().toEpochMilli().toDouble()
|
|
||||||
|
|
||||||
private fun updateSpeedLocationTime() {
|
|
||||||
val previousSpeed = this.speedState
|
|
||||||
val previousLocation = this.locationState
|
|
||||||
val currentAcceleration = this.accelerationState
|
|
||||||
val now = Instant.now().toEpochMilli().toDouble()
|
|
||||||
val timeDifference = now - this.timeState
|
|
||||||
this.timeState = now
|
|
||||||
this.speedState = Coordinates(
|
|
||||||
previousSpeed.x + timeDifference * currentAcceleration.x * 1e-3,
|
|
||||||
previousSpeed.y + timeDifference * currentAcceleration.y * 1e-3
|
|
||||||
)
|
|
||||||
val locationDifference = Coordinates(
|
|
||||||
timeDifference * 1e-3 * (previousSpeed.x + currentAcceleration.x * timeDifference * 1e-3 / 2),
|
|
||||||
timeDifference * 1e-3 * (previousSpeed.y + currentAcceleration.y * timeDifference * 1e-3 / 2)
|
|
||||||
)
|
|
||||||
this.locationState = Coordinates(
|
|
||||||
previousLocation.x + locationDifference.x,
|
|
||||||
previousLocation.y + locationDifference.y
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
|
||||||
override suspend fun open() {
|
|
||||||
super.open()
|
|
||||||
launch {
|
|
||||||
doRecurring(Duration.seconds(1)) {
|
|
||||||
carProperties.read()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
launch {
|
|
||||||
doRecurring(Duration.milliseconds(50)) {
|
|
||||||
updateSpeedLocationTime()
|
|
||||||
updateLogical(speed, this@VirtualCar.speedState)
|
|
||||||
updateLogical(acceleration, this@VirtualCar.accelerationState)
|
|
||||||
updateLogical(location, this@VirtualCar.locationState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object CoordinatesMetaConverter : MetaConverter<Coordinates> {
|
|
||||||
override fun metaToObject(meta: Meta): Coordinates = Coordinates(
|
|
||||||
meta["x"].double ?: 0.0,
|
|
||||||
meta["y"].double ?: 0.0
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun objectToMeta(obj: Coordinates): Meta = Meta {
|
|
||||||
"x" put obj.x
|
|
||||||
"y" put obj.y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : DeviceSpec<VirtualCar>(), Factory<VirtualCar> {
|
|
||||||
override fun invoke(meta: Meta, context: Context): VirtualCar = VirtualCar(context, meta)
|
|
||||||
|
|
||||||
val speed by property(CoordinatesMetaConverter) { this.speedState }
|
|
||||||
|
|
||||||
val location by property(CoordinatesMetaConverter) { this.locationState }
|
|
||||||
|
|
||||||
val acceleration by property(CoordinatesMetaConverter, VirtualCar::accelerationState)
|
|
||||||
|
|
||||||
val carProperties by metaProperty {
|
|
||||||
Meta {
|
|
||||||
val time = Instant.now()
|
|
||||||
"time" put time.toEpochMilli()
|
|
||||||
"speed" put CoordinatesMetaConverter.objectToMeta(read(speed))
|
|
||||||
"location" put CoordinatesMetaConverter.objectToMeta(read(location))
|
|
||||||
"acceleration" put CoordinatesMetaConverter.objectToMeta(read(acceleration))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -203,7 +203,7 @@ class PiMotionMasterDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val timeout by property(MetaConverter.duration, PiMotionMasterDevice::timeoutValue) {
|
val timeout by mutableProperty(MetaConverter.duration, PiMotionMasterDevice::timeoutValue) {
|
||||||
info = "Timeout"
|
info = "Timeout"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,7 @@ import javafx.beans.property.ObjectPropertyBase
|
|||||||
import javafx.beans.property.Property
|
import javafx.beans.property.Property
|
||||||
import javafx.beans.property.ReadOnlyProperty
|
import javafx.beans.property.ReadOnlyProperty
|
||||||
import ru.mipt.npm.controls.api.Device
|
import ru.mipt.npm.controls.api.Device
|
||||||
import ru.mipt.npm.controls.spec.DevicePropertySpec
|
import ru.mipt.npm.controls.spec.*
|
||||||
import ru.mipt.npm.controls.spec.WritableDevicePropertySpec
|
|
||||||
import ru.mipt.npm.controls.spec.onPropertyChange
|
|
||||||
import ru.mipt.npm.controls.spec.write
|
|
||||||
import space.kscience.dataforge.context.info
|
import space.kscience.dataforge.context.info
|
||||||
import space.kscience.dataforge.context.logger
|
import space.kscience.dataforge.context.logger
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
@ -4,6 +4,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
|||||||
enableFeaturePreview("VERSION_CATALOGS")
|
enableFeaturePreview("VERSION_CATALOGS")
|
||||||
|
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
|
|
||||||
val toolsVersion = "0.10.5"
|
val toolsVersion = "0.10.5"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@ -40,6 +41,7 @@ include(
|
|||||||
":controls-server",
|
":controls-server",
|
||||||
":controls-opcua",
|
":controls-opcua",
|
||||||
":demo",
|
":demo",
|
||||||
|
":demo:car",
|
||||||
":magix",
|
":magix",
|
||||||
":magix:magix-api",
|
":magix:magix-api",
|
||||||
":magix:magix-server",
|
":magix:magix-server",
|
||||||
|
Loading…
Reference in New Issue
Block a user