Refactor car demo. Add meta property.

This commit is contained in:
Alexander Nozik 2021-10-30 13:08:45 +03:00
parent c1a1b6e696
commit 4dc33c012d
14 changed files with 266 additions and 160 deletions

View File

@ -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
*/ */

View File

@ -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) {

View File

@ -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
}

View File

@ -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 }

View File

@ -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

View File

@ -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
View 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")
//}

View File

@ -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)
}
}

View File

@ -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()))
} }
} }
} }

View File

@ -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()

View File

@ -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))
}
}
}
}

View File

@ -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"
} }
} }

View File

@ -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.*

View File

@ -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",