From fe3958fd0822a6b6d0eaddcd89e00fa391e4d9b4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 25 Jun 2021 19:57:04 +0300 Subject: [PATCH] Add alternative device syntax --- .../kotlin/ru/mipt/npm/controls/api/Device.kt | 12 +- .../ru/mipt/npm/controls/base/DeviceBase.kt | 13 ++- .../npm/controls/controllers/DeviceManager.kt | 5 +- .../npm/controls/properties/DeviceBySpec.kt | 83 +++++++++---- .../npm/controls/properties/DeviceSpec.kt | 88 +++++++++++--- .../properties/deviceStateDelegates.kt | 12 ++ .../properties/propertySpecDelegates.kt | 105 +++++++++++++++++ .../controls/properties/getDeviceProperty.kt | 13 +++ .../npm/controls/demo/DemoControllerView.kt | 6 +- .../ru/mipt/npm/controls/demo/DemoDevice.kt | 110 ++++++++---------- .../pimotionmaster/PiMotionMasterDevice.kt | 11 +- 11 files changed, 327 insertions(+), 131 deletions(-) create mode 100644 controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/deviceStateDelegates.kt create mode 100644 controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/propertySpecDelegates.kt create mode 100644 controls-core/src/jvmMain/kotlin/ru/mipt/npm/controls/properties/getDeviceProperty.kt diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/api/Device.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/api/Device.kt index 1d8a8bb..6b974f2 100644 --- a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/api/Device.kt +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/api/Device.kt @@ -12,10 +12,11 @@ import space.kscience.dataforge.misc.Type /** - * General interface describing a managed Device + * General interface describing a managed Device. + * Device is a supervisor scope encompassing all operations on a device. When canceled, cancels all running processes. */ @Type(DEVICE_TARGET) -public interface Device : Closeable, ContextAware { +public interface Device : Closeable, ContextAware, CoroutineScope { /** * List of supported property descriptors */ @@ -27,11 +28,6 @@ public interface Device : Closeable, ContextAware { */ public val actionDescriptors: Collection - /** - * The supervisor scope encompassing all operations on a device. When canceled, cancels all running processes. - */ - public val scope: CoroutineScope - /** * Get the value of the property or throw error if property in not defined. * Suspend if property value is not available @@ -61,7 +57,7 @@ public interface Device : Closeable, ContextAware { public suspend fun execute(action: String, argument: MetaItem? = null): MetaItem? override fun close() { - scope.cancel("The device is closed") + cancel("The device is closed") } public companion object { diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/base/DeviceBase.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/base/DeviceBase.kt index 8a3375d..0033f0b 100644 --- a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/base/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/base/DeviceBase.kt @@ -13,6 +13,7 @@ import ru.mipt.npm.controls.api.PropertyDescriptor import space.kscience.dataforge.context.Context import space.kscience.dataforge.meta.MetaItem import space.kscience.dataforge.misc.DFExperimental +import kotlin.coroutines.CoroutineContext //TODO move to DataForge-core @DFExperimental @@ -28,7 +29,7 @@ private open class BasicReadOnlyDeviceProperty( private val getter: suspend (before: MetaItem?) -> MetaItem, ) : ReadOnlyDeviceProperty { - override val scope: CoroutineScope get() = device.scope + override val scope: CoroutineScope get() = device private val state: MutableStateFlow = MutableStateFlow(default) override val value: MetaItem? get() = state.value @@ -107,11 +108,11 @@ private class BasicDeviceProperty( * Baseline implementation of [Device] interface */ @Suppress("EXPERIMENTAL_API_USAGE") -public abstract class DeviceBase(override val context: Context) : Device { +public abstract class DeviceBase(final override val context: Context) : Device { + + override val coroutineContext: CoroutineContext = + context.coroutineContext + SupervisorJob(context.coroutineContext[Job]) - override val scope: CoroutineScope by lazy { - CoroutineScope(context.coroutineContext + Job(context.coroutineContext[Job])) - } private val _properties = HashMap() public val properties: Map get() = _properties @@ -219,7 +220,7 @@ public abstract class DeviceBase(override val context: Context) : Device { private val block: suspend (MetaItem?) -> MetaItem?, ) : DeviceAction { override suspend fun invoke(arg: MetaItem?): MetaItem? = - withContext(scope.coroutineContext + SupervisorJob(scope.coroutineContext[Job])) { + withContext(coroutineContext) { block(arg) } } diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/DeviceManager.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/DeviceManager.kt index 9ffb735..c9471ba 100644 --- a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/DeviceManager.kt +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/DeviceManager.kt @@ -40,16 +40,15 @@ public class DeviceManager(override val deviceName: String = "") : AbstractPlugi } } -public interface DeviceSpec : Factory -public fun DeviceManager.install(name: String, factory: DeviceSpec, meta: Meta = Meta.EMPTY): D { +public fun DeviceManager.install(name: String, factory: Factory, meta: Meta = Meta.EMPTY): D { val device = factory(meta, context) registerDevice(NameToken(name), device) return device } public fun DeviceManager.installing( - factory: DeviceSpec, + factory: Factory, metaBuilder: MetaBuilder.() -> Unit = {}, ): ReadOnlyProperty = ReadOnlyProperty { _, property -> val name = property.name diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceBySpec.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceBySpec.kt index 7f7f478..658884a 100644 --- a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceBySpec.kt +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceBySpec.kt @@ -1,11 +1,8 @@ package ru.mipt.npm.controls.properties -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import ru.mipt.npm.controls.api.ActionDescriptor @@ -15,22 +12,28 @@ import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Global import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.MetaItem +import space.kscience.dataforge.meta.transformations.MetaConverter +import kotlin.coroutines.CoroutineContext +import kotlin.properties.Delegates.observable +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty /** * @param D recursive self-type for properties and actions */ -public open class DeviceBySpec> : Device { - override var context: Context = Global +public open class DeviceBySpec>( + public val spec: DeviceSpec, + context: Context = Global, + meta: Meta = Meta.EMPTY +) : Device { + override var context: Context = context internal set - public var meta: Meta = Meta.EMPTY + public var meta: Meta = meta internal set - public var properties: Map> = emptyMap() - internal set - - public var actions: Map> = emptyMap() - internal set + public val properties: Map> get() = spec.properties + public val actions: Map> get() = spec.actions override val propertyDescriptors: Collection get() = properties.values.map { it.descriptor } @@ -38,7 +41,9 @@ public open class DeviceBySpec> : Device { override val actionDescriptors: Collection get() = actions.values.map { it.descriptor } - override val scope: CoroutineScope get() = context + override val coroutineContext: CoroutineContext by lazy { + context.coroutineContext + SupervisorJob(context.coroutineContext[Job]) + } private val logicalState: HashMap = HashMap() @@ -54,7 +59,7 @@ public open class DeviceBySpec> : Device { private val stateLock = Mutex() - internal suspend fun setLogicalState(propertyName: String, value: MetaItem?) { + internal suspend fun updateLogical(propertyName: String, value: MetaItem?) { if (value != logicalState[propertyName]) { stateLock.withLock { logicalState[propertyName] = value @@ -66,12 +71,13 @@ public open class DeviceBySpec> : Device { } /** - * Force read physical value and push an update if it is changed + * Force read physical value and push an update if it is changed. It does not matter if logical state is present. + * The logical state is updated after read */ public suspend fun readProperty(propertyName: String): MetaItem { val newValue = properties[propertyName]?.readItem(self) ?: error("A property with name $propertyName is not registered in $this") - setLogicalState(propertyName, newValue) + updateLogical(propertyName, newValue) return newValue } @@ -84,31 +90,58 @@ public open class DeviceBySpec> : Device { } } - override suspend fun setProperty(propertyName: String, value: MetaItem) { + override suspend fun setProperty(propertyName: String, value: MetaItem): Unit { //If there is a physical property with given name, invalidate logical property and write physical one (properties[propertyName] as? WritableDevicePropertySpec)?.let { it.writeItem(self, value) invalidateProperty(propertyName) } ?: run { - setLogicalState(propertyName, value) + updateLogical(propertyName, value) } } override suspend fun execute(action: String, argument: MetaItem?): MetaItem? = actions[action]?.executeItem(self, argument) -} -public operator fun , T : Any> D.get( - propertySpec: DevicePropertySpec -): Deferred = scope.async { - propertySpec.read(this@get).also { - setLogicalState(propertySpec.name, propertySpec.converter.objectToMetaItem(it)) + /** + * A delegate that represents the logical-only state of the device + */ + public fun state( + converter: MetaConverter, + initialValue: T, + ): ReadWriteProperty = observable(initialValue) { property: KProperty<*>, oldValue: T, newValue: T -> + if (oldValue != newValue) { + launch { + invalidateProperty(property.name) + _propertyFlow.emit(property.name to converter.objectToMetaItem(newValue)) + } + } + } + + public suspend fun DevicePropertySpec.read(): T = read(self) + + override fun close() { + with(spec){ self.onShutdown() } + super.close() } } +public suspend fun , T : Any> D.getSuspend( + propertySpec: DevicePropertySpec +): T = propertySpec.read(this@getSuspend).also { + updateLogical(propertySpec.name, propertySpec.converter.objectToMetaItem(it)) +} + + +public fun , T : Any> D.getAsync( + propertySpec: DevicePropertySpec +): Deferred = async { + getSuspend(propertySpec) +} + public operator fun , T : Any> D.set(propertySpec: WritableDevicePropertySpec, value: T) { - scope.launch { + launch { propertySpec.write(this@set, value) invalidateProperty(propertySpec.name) } diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceSpec.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceSpec.kt index f1cd375..33ad3e9 100644 --- a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceSpec.kt +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceSpec.kt @@ -1,6 +1,6 @@ package ru.mipt.npm.controls.properties -import kotlinx.coroutines.Deferred +import kotlinx.coroutines.withContext import ru.mipt.npm.controls.api.ActionDescriptor import ru.mipt.npm.controls.api.PropertyDescriptor import space.kscience.dataforge.context.Context @@ -9,13 +9,58 @@ import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.transformations.MetaConverter import kotlin.properties.PropertyDelegateProvider import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty +import kotlin.reflect.KProperty1 public abstract class DeviceSpec>( private val buildDevice: () -> D ) : Factory { - private val deviceProperties = HashMap>() - private val deviceActions = HashMap>() + private val _properties = HashMap>() + public val properties: Map> get() = _properties + + private val _actions = HashMap>() + public val actions: Map> get() = _actions + + public fun registerProperty(deviceProperty: DevicePropertySpec): DevicePropertySpec { + _properties[deviceProperty.name] = deviceProperty + return deviceProperty + } + + public fun registerProperty( + converter: MetaConverter, + readOnlyProperty: KProperty1, + descriptorBuilder: PropertyDescriptor.() -> Unit = {} + ): DevicePropertySpec { + val deviceProperty = object : DevicePropertySpec { + override val name: String = readOnlyProperty.name + override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder) + override val converter: MetaConverter = converter + override suspend fun read(device: D): T = + withContext(device.coroutineContext) { readOnlyProperty.get(device) } + } + return registerProperty(deviceProperty) + } + + public fun registerProperty( + converter: MetaConverter, + readWriteProperty: KMutableProperty1, + descriptorBuilder: PropertyDescriptor.() -> Unit = {} + ): WritableDevicePropertySpec { + val deviceProperty = object : WritableDevicePropertySpec { + override val name: String = readWriteProperty.name + override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder) + override val converter: MetaConverter = converter + override suspend fun read(device: D): T = + withContext(device.coroutineContext) { readWriteProperty.get(device) } + + override suspend fun write(device: D, value: T) = withContext(device.coroutineContext) { + readWriteProperty.set(device, value) + } + } + registerProperty(deviceProperty) + return deviceProperty + } public fun property( converter: MetaConverter, @@ -30,15 +75,14 @@ public abstract class DeviceSpec>( override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder) override val converter: MetaConverter = converter - override suspend fun read(device: D): T = device.read() + override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() } } - deviceProperties[propertyName] = deviceProperty + _properties[propertyName] = deviceProperty ReadOnlyProperty, DevicePropertySpec> { _, _ -> deviceProperty } } - public fun property( converter: MetaConverter, name: String? = null, @@ -53,19 +97,24 @@ public abstract class DeviceSpec>( override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder) override val converter: MetaConverter = converter - override suspend fun read(device: D): T = device.read() + override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() } - override suspend fun write(device: D, value: T) { + override suspend fun write(device: D, value: T) = withContext(device.coroutineContext) { device.write(value) } } - deviceProperties[propertyName] = deviceProperty + _properties[propertyName] = deviceProperty ReadOnlyProperty, WritableDevicePropertySpec> { _, _ -> deviceProperty } } + public fun registerAction(deviceAction: DeviceActionSpec): DeviceActionSpec { + _actions[deviceAction.name] = deviceAction + return deviceAction + } + public fun action( inputConverter: MetaConverter, outputConverter: MetaConverter, @@ -82,21 +131,30 @@ public abstract class DeviceSpec>( override val inputConverter: MetaConverter = inputConverter override val outputConverter: MetaConverter = outputConverter - override suspend fun execute(device: D, input: I?): O? { - return device.execute(input) + override suspend fun execute(device: D, input: I?): O? = withContext(device.coroutineContext) { + device.execute(input) } } - deviceActions[actionName] = deviceAction + _actions[actionName] = deviceAction ReadOnlyProperty, DeviceActionSpec> { _, _ -> deviceAction } } + /** + * The function is executed right after device initialization is finished + */ + public open fun D.onStartup(){} + + /** + * The function is executed before device is shut down + */ + public open fun D.onShutdown(){} + override fun invoke(meta: Meta, context: Context): D = buildDevice().apply { this.context = context this.meta = meta - this.properties = deviceProperties - this.actions = deviceActions + onStartup() } -} \ No newline at end of file +} diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/deviceStateDelegates.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/deviceStateDelegates.kt new file mode 100644 index 0000000..f3f4c69 --- /dev/null +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/deviceStateDelegates.kt @@ -0,0 +1,12 @@ +package ru.mipt.npm.controls.properties + +import space.kscience.dataforge.meta.transformations.MetaConverter +import kotlin.properties.ReadWriteProperty + +public fun > D.state( + initialValue: Double, +): ReadWriteProperty = state(MetaConverter.double, initialValue) + +public fun > D.state( + initialValue: Number, +): ReadWriteProperty = state(MetaConverter.number, initialValue) \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/propertySpecDelegates.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/propertySpecDelegates.kt new file mode 100644 index 0000000..911a8a4 --- /dev/null +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/propertySpecDelegates.kt @@ -0,0 +1,105 @@ +package ru.mipt.npm.controls.properties + +import ru.mipt.npm.controls.api.PropertyDescriptor +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.MetaItem +import space.kscience.dataforge.meta.TypedMetaItem +import space.kscience.dataforge.meta.transformations.MetaConverter +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadOnlyProperty + +//read only delegates + +public fun > DeviceSpec.booleanProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> Boolean +): PropertyDelegateProvider, ReadOnlyProperty, DevicePropertySpec>> = + property(MetaConverter.boolean, name, descriptorBuilder, read) + + +public fun > DeviceSpec.numberProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> Number +): PropertyDelegateProvider, ReadOnlyProperty, DevicePropertySpec>> = + property(MetaConverter.number, name, descriptorBuilder, read) + +public fun > DeviceSpec.doubleProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> Double +): PropertyDelegateProvider, ReadOnlyProperty, DevicePropertySpec>> = + property(MetaConverter.double, name, descriptorBuilder, read) + +public fun > DeviceSpec.stringProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> String +): PropertyDelegateProvider, ReadOnlyProperty, DevicePropertySpec>> = + property(MetaConverter.string, name, descriptorBuilder, read) + +public fun > DeviceSpec.itemProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> MetaItem +): PropertyDelegateProvider, ReadOnlyProperty, DevicePropertySpec>> = + property(MetaConverter.item, name, descriptorBuilder, read) + +public fun > DeviceSpec.metaProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> Meta +): PropertyDelegateProvider, ReadOnlyProperty, DevicePropertySpec>> = + property(MetaConverter.meta, name, descriptorBuilder, read) + +//read-write delegates + +public fun > DeviceSpec.booleanProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> Boolean, + write: suspend D.(Boolean) -> Unit +): PropertyDelegateProvider, ReadOnlyProperty, WritableDevicePropertySpec>> = + property(MetaConverter.boolean, name, descriptorBuilder, read, write) + + +public fun > DeviceSpec.numberProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> Number, + write: suspend D.(Number) -> Unit +): PropertyDelegateProvider, ReadOnlyProperty, WritableDevicePropertySpec>> = + property(MetaConverter.number, name, descriptorBuilder, read, write) + +public fun > DeviceSpec.doubleProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> Double, + write: suspend D.(Double) -> Unit +): PropertyDelegateProvider, ReadOnlyProperty, WritableDevicePropertySpec>> = + property(MetaConverter.double, name, descriptorBuilder, read, write) + +public fun > DeviceSpec.stringProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> String, + write: suspend D.(String) -> Unit +): PropertyDelegateProvider, ReadOnlyProperty, WritableDevicePropertySpec>> = + property(MetaConverter.string, name, descriptorBuilder, read, write) + +public fun > DeviceSpec.itemProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> MetaItem, + write: suspend D.(MetaItem) -> Unit +): PropertyDelegateProvider, ReadOnlyProperty, WritableDevicePropertySpec>>> = + property(MetaConverter.item, name, descriptorBuilder, read, write) + +public fun > DeviceSpec.metaProperty( + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> Meta, + write: suspend D.(Meta) -> Unit +): PropertyDelegateProvider, ReadOnlyProperty, WritableDevicePropertySpec>> = + property(MetaConverter.meta, name, descriptorBuilder, read, write) \ No newline at end of file diff --git a/controls-core/src/jvmMain/kotlin/ru/mipt/npm/controls/properties/getDeviceProperty.kt b/controls-core/src/jvmMain/kotlin/ru/mipt/npm/controls/properties/getDeviceProperty.kt new file mode 100644 index 0000000..20b1ded --- /dev/null +++ b/controls-core/src/jvmMain/kotlin/ru/mipt/npm/controls/properties/getDeviceProperty.kt @@ -0,0 +1,13 @@ +package ru.mipt.npm.controls.properties + +import kotlinx.coroutines.runBlocking +import ru.mipt.npm.controls.api.PropertyDescriptor +import space.kscience.dataforge.meta.transformations.MetaConverter +import kotlin.reflect.KFunction + +/** + * Blocking property get call + */ +public operator fun , T : Any> D.get( + propertySpec: DevicePropertySpec +): T = runBlocking { getAsync(propertySpec).await() } \ No newline at end of file diff --git a/demo/src/main/kotlin/ru/mipt/npm/controls/demo/DemoControllerView.kt b/demo/src/main/kotlin/ru/mipt/npm/controls/demo/DemoControllerView.kt index c1390d0..e09436f 100644 --- a/demo/src/main/kotlin/ru/mipt/npm/controls/demo/DemoControllerView.kt +++ b/demo/src/main/kotlin/ru/mipt/npm/controls/demo/DemoControllerView.kt @@ -95,9 +95,9 @@ class DemoControllerView : View(title = " Demo controller remote") { useMaxWidth = true action { controller.device?.apply { - timeScaleValue = timeScaleSlider.value - sinScaleValue = xScaleSlider.value - cosScaleValue = yScaleSlider.value + timeScale = timeScaleSlider.value + sinScale = xScaleSlider.value + cosScale = yScaleSlider.value } } } diff --git a/demo/src/main/kotlin/ru/mipt/npm/controls/demo/DemoDevice.kt b/demo/src/main/kotlin/ru/mipt/npm/controls/demo/DemoDevice.kt index 484551c..b678ed2 100644 --- a/demo/src/main/kotlin/ru/mipt/npm/controls/demo/DemoDevice.kt +++ b/demo/src/main/kotlin/ru/mipt/npm/controls/demo/DemoDevice.kt @@ -1,73 +1,59 @@ package ru.mipt.npm.controls.demo -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.asCoroutineDispatcher -import ru.mipt.npm.controls.base.* -import ru.mipt.npm.controls.controllers.DeviceSpec -import ru.mipt.npm.controls.controllers.double -import space.kscience.dataforge.context.Context +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import ru.mipt.npm.controls.properties.* import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.values.asValue +import space.kscience.dataforge.meta.transformations.MetaConverter import java.time.Instant -import java.util.concurrent.Executors -import kotlin.math.cos -import kotlin.math.sin -import kotlin.time.Duration -import kotlin.time.ExperimentalTime - -@OptIn(ExperimentalTime::class) -class DemoDevice(context: Context) : DeviceBase(context) { - - private val executor = Executors.newSingleThreadExecutor() - - override val scope: CoroutineScope = CoroutineScope( - context.coroutineContext + executor.asCoroutineDispatcher() + Job(context.coroutineContext[Job]) - ) - - val timeScale: DeviceProperty by writingVirtual(5000.0.asValue()) - var timeScaleValue by timeScale.double() - - val sinScale by writingVirtual(1.0.asValue()) - var sinScaleValue by sinScale.double() - val sin: TypedReadOnlyDeviceProperty by readingNumber { - val time = Instant.now() - sin(time.toEpochMilli().toDouble() / timeScaleValue) * sinScaleValue - } - - val cosScale by writingVirtual(1.0.asValue()) - var cosScaleValue by cosScale.double() - val cos by readingNumber { - val time = Instant.now() - cos(time.toEpochMilli().toDouble() / timeScaleValue) * cosScaleValue - } - - val coordinates by readingMeta { - val time = Instant.now() - "time" put time.toEpochMilli() - "x" put sin(time.toEpochMilli().toDouble() / timeScaleValue) * sinScaleValue - "y" put cos(time.toEpochMilli().toDouble() / timeScaleValue) * cosScaleValue - } - val resetScale: DeviceAction by acting { - timeScaleValue = 5000.0 - sinScaleValue = 1.0 - cosScaleValue = 1.0 - } +class DemoDevice : DeviceBySpec(DemoDevice) { + var timeScale by state(5000.0) + var sinScale by state( 1.0) + var cosScale by state(1.0) - init { - sin.readEvery(Duration.seconds(0.2)) - cos.readEvery(Duration.seconds(0.2)) - coordinates.readEvery(Duration.seconds(0.3)) - } + companion object : DeviceSpec(::DemoDevice) { + // register virtual properties based on actual object state + val timeScaleProperty = registerProperty(MetaConverter.double, DemoDevice::timeScale) + val sinScaleProperty = registerProperty(MetaConverter.double, DemoDevice::sinScale) + val cosScaleProperty = registerProperty(MetaConverter.double, DemoDevice::cosScale) - override fun close() { - super.close() - executor.shutdown() - } + val sin by doubleProperty { + val time = Instant.now() + kotlin.math.sin(time.toEpochMilli().toDouble() / timeScale) * sinScale + } - companion object : DeviceSpec { - override fun invoke(meta: Meta, context: Context): DemoDevice = DemoDevice(context) + val cos by doubleProperty { + val time = Instant.now() + kotlin.math.cos(time.toEpochMilli().toDouble() / timeScale) * sinScale + } + + val coordinates by metaProperty { + Meta { + val time = Instant.now() + "time" put time.toEpochMilli() + "x" put getSuspend(sin) + "y" put getSuspend(cos) + } + } + + val resetScale by action(MetaConverter.meta, MetaConverter.meta) { + timeScale = 5000.0 + sinScale = 1.0 + cosScale = 1.0 + null + } + + override fun DemoDevice.onStartup() { + launch { + while(isActive){ + delay(50) + sin.read() + cos.read() + } + } + } } } \ No newline at end of file diff --git a/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt b/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt index be36ed8..4cab816 100644 --- a/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt +++ b/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.sync.withLock import ru.mipt.npm.controls.api.DeviceHub import ru.mipt.npm.controls.api.PropertyDescriptor import ru.mipt.npm.controls.base.* -import ru.mipt.npm.controls.controllers.DeviceSpec import ru.mipt.npm.controls.controllers.duration import ru.mipt.npm.controls.ports.* import space.kscience.dataforge.context.* @@ -28,10 +27,6 @@ class PiMotionMasterDevice( private val portFactory: PortFactory = KtorTcpPort, ) : DeviceBase(context), DeviceHub { - override val scope: CoroutineScope = CoroutineScope( - context.coroutineContext + SupervisorJob(context.coroutineContext[Job]) - ) - private var port: Port? = null //TODO make proxy work //PortProxy { portFactory(address ?: error("The device is not connected"), context) } @@ -151,11 +146,10 @@ class PiMotionMasterDevice( withTimeout(timeoutValue) { sendCommandInternal(command, *arguments) val phrases = port?.receiving()?.withDelimiter("\n") ?: error("Not connected to device") - val list = phrases.transformWhile { line -> + phrases.transformWhile { line -> emit(line) line.endsWith(" \n") }.toList() - list } } catch (ex: Throwable) { logger.warn { "Error during PIMotionMaster request. Requesting error code." } @@ -204,7 +198,6 @@ class PiMotionMasterDevice( ) inner class Axis(val axisId: String) : DeviceBase(context) { - override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope private suspend fun readAxisBoolean(command: String): Boolean = requestAndParse(command, axisId)[axisId]?.toIntOrNull() @@ -343,7 +336,7 @@ class PiMotionMasterDevice( } } - companion object : DeviceSpec { + companion object : Factory { override fun invoke(meta: Meta, context: Context): PiMotionMasterDevice = PiMotionMasterDevice(context) }