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)
public interface Device : Closeable, ContextAware, CoroutineScope {
/**
* Initial configuration meta for the device
*/
public val meta: Meta get() = Meta.EMPTY
/**
* List of supported property descriptors
*/

View File

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

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

View File

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

View File

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

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

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

View File

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

View File

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