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)
|
||||
public interface Device : Closeable, ContextAware, CoroutineScope {
|
||||
|
||||
/**
|
||||
* Initial configuration meta for the device
|
||||
*/
|
||||
public val meta: Meta get() = Meta.EMPTY
|
||||
|
||||
/**
|
||||
* List of supported property descriptors
|
||||
*/
|
||||
|
@ -17,7 +17,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
override val context: Context = Global,
|
||||
public val meta: Meta = Meta.EMPTY
|
||||
override val meta: Meta = Meta.EMPTY
|
||||
) : Device {
|
||||
|
||||
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
|
||||
*/
|
||||
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||
public val spec: DeviceSpec<D>,
|
||||
public val spec: DeviceSpec<in D>,
|
||||
context: Context = Global,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : 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
|
||||
*/
|
||||
@RequiresOptIn
|
||||
@RequiresOptIn("This API should not be called outside of Device internals")
|
||||
public annotation class InternalDeviceAPI
|
||||
|
||||
public interface DevicePropertySpec<in D : Device, T> {
|
||||
/**
|
||||
* Property name, should be unique in device
|
||||
*/
|
||||
public val name: String
|
||||
|
||||
/**
|
||||
* Property descriptor
|
||||
*/
|
||||
@ -43,6 +38,11 @@ public interface DevicePropertySpec<in D : Device, 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)
|
||||
public suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta =
|
||||
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> {
|
||||
/**
|
||||
* Action name, should be unique in device
|
||||
*/
|
||||
public val name: String
|
||||
|
||||
/**
|
||||
* Action descriptor
|
||||
*/
|
||||
@ -82,9 +77,14 @@ public interface DeviceActionSpec<in D : Device, 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(
|
||||
device: D,
|
||||
item: Meta?
|
||||
item: Meta?,
|
||||
): Meta? {
|
||||
val arg = item?.let { inputConverter.metaToObject(item) }
|
||||
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(
|
||||
propertySpec: DevicePropertySpec<D, T>
|
||||
propertySpec: DevicePropertySpec<D, T>,
|
||||
): T = propertySpec.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))
|
||||
?: error("Property meta converter returned null")
|
||||
|
||||
public fun <D : Device, T> D.write(
|
||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||
value: T
|
||||
value: T,
|
||||
): Job = launch {
|
||||
writeProperty(propertySpec.name, propertySpec.converter.objectToMeta(value))
|
||||
}
|
||||
|
||||
public fun <D : DeviceBase<D>, T> D.write(
|
||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||
value: T
|
||||
value: T,
|
||||
): Job = launch {
|
||||
propertySpec.write(value)
|
||||
}
|
||||
@ -120,7 +120,7 @@ public fun <D : DeviceBase<D>, T> D.write(
|
||||
*/
|
||||
public fun <D : Device, T> Device.onPropertyChange(
|
||||
spec: DevicePropertySpec<D, T>,
|
||||
callback: suspend PropertyChangedMessage.(T?) -> Unit
|
||||
callback: suspend PropertyChangedMessage.(T?) -> Unit,
|
||||
): Job = messageFlow
|
||||
.filterIsInstance<PropertyChangedMessage>()
|
||||
.filter { it.property == spec.name }
|
||||
|
@ -14,7 +14,10 @@ import kotlin.reflect.KProperty1
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
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
|
||||
|
||||
private val _actions = HashMap<String, DeviceActionSpec<D, *, *>>()
|
||||
@ -31,8 +34,7 @@ public abstract class DeviceSpec<D : Device> {
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): DevicePropertySpec<D, T> {
|
||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||
override val name: String = readOnlyProperty.name
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(readOnlyProperty.name).apply(descriptorBuilder)
|
||||
override val converter: MetaConverter<T> = converter
|
||||
override suspend fun read(device: D): T =
|
||||
withContext(device.coroutineContext) { readOnlyProperty.get(device) }
|
||||
@ -41,15 +43,38 @@ public abstract class DeviceSpec<D : Device> {
|
||||
}
|
||||
|
||||
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>,
|
||||
readWriteProperty: KMutableProperty1<D, T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _, property ->
|
||||
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
|
||||
writable = true
|
||||
}.apply(descriptorBuilder)
|
||||
@ -79,8 +104,7 @@ public abstract class DeviceSpec<D : Device> {
|
||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||
val propertyName = name ?: property.name
|
||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||
override val name: String = propertyName
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
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>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
@ -101,8 +125,7 @@ public abstract class DeviceSpec<D : Device> {
|
||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
||||
val propertyName = name ?: property.name
|
||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||
override val name: String = propertyName
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
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 ->
|
||||
val actionName = name ?: property.name
|
||||
val deviceAction = object : DeviceActionSpec<D, I, O> {
|
||||
override val name: String = actionName
|
||||
override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply(descriptorBuilder)
|
||||
|
||||
override val inputConverter: MetaConverter<I> = inputConverter
|
||||
|
@ -97,7 +97,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
||||
read: suspend D.() -> Boolean,
|
||||
write: suspend D.(Boolean) -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
||||
property(
|
||||
mutableProperty(
|
||||
MetaConverter.boolean,
|
||||
{
|
||||
metaDescriptor {
|
||||
@ -117,7 +117,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
||||
read: suspend D.() -> Number,
|
||||
write: suspend D.(Number) -> Unit
|
||||
): 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(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
@ -125,7 +125,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
||||
read: suspend D.() -> Double,
|
||||
write: suspend D.(Double) -> Unit
|
||||
): 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(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
@ -133,7 +133,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
||||
read: suspend D.() -> String,
|
||||
write: suspend D.(String) -> Unit
|
||||
): 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(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
@ -141,4 +141,4 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
||||
read: suspend D.() -> Meta,
|
||||
write: suspend D.(Meta) -> Unit
|
||||
): 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.scene.Parent
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.stage.Stage
|
||||
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.install
|
||||
import ru.mipt.npm.controls.demo.virtual_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 ru.mipt.npm.controls.demo.car.VirtualCar.Companion.acceleration
|
||||
import space.kscience.dataforge.context.*
|
||||
import tornadofx.*
|
||||
|
||||
class VirtualCarController : Controller(), ContextAware {
|
||||
|
||||
var device: VirtualCar? = null
|
||||
var magixServer: ApplicationEngine? = null
|
||||
|
||||
override val context = Context("demoDevice") {
|
||||
plugin(DeviceManager)
|
||||
@ -32,18 +25,11 @@ class VirtualCarController : Controller(), ContextAware {
|
||||
fun init() {
|
||||
context.launch {
|
||||
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() {
|
||||
logger.info { "Shutting down..." }
|
||||
magixServer?.stop(1000, 5000)
|
||||
logger.info { "Magix server stopped" }
|
||||
device?.close()
|
||||
logger.info { "Device server stopped" }
|
||||
context.close()
|
||||
@ -80,7 +66,8 @@ class VirtualCarControllerView : View(title = " Virtual car controller remote")
|
||||
action {
|
||||
controller.device?.run {
|
||||
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)
|
||||
|
||||
// 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 {
|
||||
type(ValueType.NUMBER)
|
||||
}
|
||||
info = "Real to virtual time scale"
|
||||
}
|
||||
|
||||
val sinScale by property(MetaConverter.double, DemoDevice::sinScaleState)
|
||||
val cosScale by property(MetaConverter.double, DemoDevice::cosScaleState)
|
||||
val sinScale by mutableProperty(MetaConverter.double, DemoDevice::sinScaleState)
|
||||
val cosScale by mutableProperty(MetaConverter.double, DemoDevice::cosScaleState)
|
||||
|
||||
val sin by doubleProperty {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,7 @@ import javafx.beans.property.ObjectPropertyBase
|
||||
import javafx.beans.property.Property
|
||||
import javafx.beans.property.ReadOnlyProperty
|
||||
import ru.mipt.npm.controls.api.Device
|
||||
import ru.mipt.npm.controls.spec.DevicePropertySpec
|
||||
import ru.mipt.npm.controls.spec.WritableDevicePropertySpec
|
||||
import ru.mipt.npm.controls.spec.onPropertyChange
|
||||
import ru.mipt.npm.controls.spec.write
|
||||
import ru.mipt.npm.controls.spec.*
|
||||
import space.kscience.dataforge.context.info
|
||||
import space.kscience.dataforge.context.logger
|
||||
import tornadofx.*
|
||||
|
@ -4,6 +4,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
enableFeaturePreview("VERSION_CATALOGS")
|
||||
|
||||
pluginManagement {
|
||||
|
||||
val toolsVersion = "0.10.5"
|
||||
|
||||
repositories {
|
||||
@ -40,6 +41,7 @@ include(
|
||||
":controls-server",
|
||||
":controls-opcua",
|
||||
":demo",
|
||||
":demo:car",
|
||||
":magix",
|
||||
":magix:magix-api",
|
||||
":magix:magix-server",
|
||||
|
Loading…
Reference in New Issue
Block a user