From 1619fdadf23e033b049d7ad3b724f8acffa012bb Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 25 Oct 2023 22:31:36 +0300 Subject: [PATCH] Refactoring. Developing composer --- build.gradle.kts | 2 +- controls-constructor/build.gradle.kts | 20 +++ .../kscience/controls/constructor/Drive.kt | 51 ++++++ .../controls/constructor/LimitSwitch.kt | 31 ++++ .../controls/constructor/PidRegulator.kt | 146 ++++++++++++++++++ .../space/kscience/controls/api/Device.kt | 18 ++- .../kscience/controls/api/DeviceMessage.kt | 8 +- .../kscience/controls/api/descriptors.kt | 4 +- .../controls/manager/DeviceManager.kt | 2 +- .../kscience/controls/spec/DeviceBase.kt | 58 +++++-- .../kscience/controls/spec/DeviceBySpec.kt | 7 +- .../controls/spec/DevicePropertySpec.kt | 4 +- .../kscience/controls/spec/DeviceSpec.kt | 8 +- .../kscience/controls/spec/DeviceTree.kt | 36 +++++ .../controls/modbus/DeviceProcessImage.kt | 2 +- .../controls/modbus/ModbusDeviceBySpec.kt | 6 +- .../opcua/client/OpcUaDeviceBySpec.kt | 3 +- .../controls/opcua/server/DeviceNameSpace.kt | 4 +- .../controls/opcua/client/OpcUaClientTest.kt | 7 +- .../controls/demo/DemoControllerView.kt | 2 +- .../kscience/controls/demo/DemoDevice.kt | 2 +- .../controls/demo/car/MagixVirtualCar.kt | 9 +- .../kscience/controls/demo/car/VirtualCar.kt | 3 +- .../controls/demo/car/VirtualCarController.kt | 4 +- .../pimotionmaster/PiMotionMasterDevice.kt | 24 +-- gradle.properties | 2 +- settings.gradle.kts | 1 + 27 files changed, 388 insertions(+), 76 deletions(-) create mode 100644 controls-constructor/build.gradle.kts create mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/Drive.kt create mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/LimitSwitch.kt create mode 100644 controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/PidRegulator.kt create mode 100644 controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceTree.kt diff --git a/build.gradle.kts b/build.gradle.kts index 347a66e..4ca88d6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ val xodusVersion by extra("2.0.1") allprojects { group = "space.kscience" - version = "0.2.2-dev-3" + version = "0.3.0-dev-1" repositories{ maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") } diff --git a/controls-constructor/build.gradle.kts b/controls-constructor/build.gradle.kts new file mode 100644 index 0000000..5815444 --- /dev/null +++ b/controls-constructor/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("space.kscience.gradle.mpp") + `maven-publish` +} + +description = """ + A low-code constructor foe composite devices simulation +""".trimIndent() + +kscience{ + jvm() + js() + dependencies { + api(projects.controlsCore) + } +} + +readme{ + maturity = space.kscience.gradle.Maturity.PROTOTYPE +} diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/Drive.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/Drive.kt new file mode 100644 index 0000000..ede628a --- /dev/null +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/Drive.kt @@ -0,0 +1,51 @@ +package center.sciprog.controls.devices.misc + +import kotlinx.coroutines.Job +import space.kscience.controls.api.Device +import space.kscience.controls.spec.DeviceBySpec +import space.kscience.controls.spec.DevicePropertySpec +import space.kscience.controls.spec.DeviceSpec +import space.kscience.controls.spec.doubleProperty +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.meta.transformations.MetaConverter + + +/** + * A single axis drive + */ +public interface Drive : Device { + /** + * Get or set target value + */ + public var target: Double + + /** + * Current position value + */ + public val position: Double + + public companion object : DeviceSpec() { + public val target: DevicePropertySpec by property(MetaConverter.double, Drive::target) + + public val position: DevicePropertySpec by doubleProperty { position } + } +} + +/** + * Virtual [Drive] with speed limit + */ +public class VirtualDrive( + context: Context, + value: Double, + private val speed: Double, +) : DeviceBySpec(Drive, context), Drive { + + private var moveJob: Job? = null + + override var position: Double = value + private set + + override var target: Double = value + + +} \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/LimitSwitch.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/LimitSwitch.kt new file mode 100644 index 0000000..6e82064 --- /dev/null +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/LimitSwitch.kt @@ -0,0 +1,31 @@ +package center.sciprog.controls.devices.misc + +import space.kscience.controls.api.Device +import space.kscience.controls.spec.DeviceBySpec +import space.kscience.controls.spec.DevicePropertySpec +import space.kscience.controls.spec.DeviceSpec +import space.kscience.controls.spec.booleanProperty +import space.kscience.dataforge.context.Context + + +/** + * A limit switch device + */ +public interface LimitSwitch : Device { + + public val locked: Boolean + + public companion object : DeviceSpec() { + public val locked: DevicePropertySpec by booleanProperty { locked } + } +} + +/** + * Virtual [LimitSwitch] + */ +public class VirtualLimitSwitch( + context: Context, + private val lockedFunction: () -> Boolean, +) : DeviceBySpec(LimitSwitch, context), LimitSwitch { + override val locked: Boolean get() = lockedFunction() +} \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/PidRegulator.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/PidRegulator.kt new file mode 100644 index 0000000..8dbe6ba --- /dev/null +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/PidRegulator.kt @@ -0,0 +1,146 @@ +package center.sciprog.controls.devices.misc + +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import space.kscience.controls.api.Device +import space.kscience.controls.spec.DeviceBySpec +import space.kscience.controls.spec.DeviceSpec +import space.kscience.controls.spec.doubleProperty +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 kotlin.math.pow +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit + + +interface PidRegulator : Device { + /** + * Proportional coefficient + */ + val kp: Double + + /** + * Integral coefficient + */ + val ki: Double + + /** + * Differential coefficient + */ + val kd: Double + + /** + * The target value for PID + */ + var target: Double + + /** + * Read current value + */ + suspend fun read(): Double + + companion object : DeviceSpec() { + val target by property(MetaConverter.double, PidRegulator::target) + val value by doubleProperty { read() } + } +} + +/** + * + */ +class VirtualPid( + context: Context, + override val kp: Double, + override val ki: Double, + override val kd: Double, + val mass: Double, + override var target: Double = 0.0, + private val dt: Duration = 0.5.milliseconds, + private val clock: Clock = Clock.System, +) : DeviceBySpec(PidRegulator, context), PidRegulator { + + private val mutex = Mutex() + + + private var lastTime: Instant = clock.now() + private var lastValue: Double = target + + private var value: Double = target + private var velocity: Double = 0.0 + private var acceleration: Double = 0.0 + private var integral: Double = 0.0 + + + private var updateJob: Job? = null + + override suspend fun onStart() { + updateJob = launch { + while (isActive) { + delay(dt) + mutex.withLock { + val realTime = clock.now() + val delta = target - value + val dtSeconds = (realTime - lastTime).toDouble(DurationUnit.SECONDS) + integral += delta * dtSeconds + val derivative = (value - lastValue) / dtSeconds + + //set last time and value to new values + lastTime = realTime + lastValue = value + + // compute new value based on velocity and acceleration from the previous step + value += velocity * dtSeconds + acceleration * dtSeconds.pow(2) / 2 + + // compute new velocity based on acceleration on the previous step + velocity += acceleration * dtSeconds + + //compute force for the next step based on current values + acceleration = (kp * delta + ki * integral + kd * derivative) / mass + + + check(value.isFinite() && velocity.isFinite()) { + "Value $value is not finite" + } + } + } + } + } + + override fun onStop() { + updateJob?.cancel() + super.stop() + } + + override suspend fun read(): Double = value + + suspend fun readVelocity(): Double = velocity + + suspend fun readAcceleration(): Double = acceleration + + suspend fun write(newTarget: Double) = mutex.withLock { + require(newTarget.isFinite()) { "Value $newTarget is not valid" } + target = newTarget + } + + companion object : Factory { + override fun build(context: Context, meta: Meta) = VirtualPid( + context, + meta["kp"].double ?: error("Kp is not defined"), + meta["ki"].double ?: error("Ki is not defined"), + meta["kd"].double ?: error("Kd is not defined"), + meta["m"].double ?: error("Mass is not defined"), + ) + + } +} \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt index 8a19e68..dad1d90 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt @@ -20,8 +20,19 @@ import space.kscience.dataforge.names.Name * A lifecycle state of a device */ public enum class DeviceLifecycleState{ + /** + * Device is initializing + */ INIT, + + /** + * The Device is initialized and running + */ OPEN, + + /** + * The Device is closed + */ CLOSED } @@ -31,13 +42,14 @@ public enum class DeviceLifecycleState{ * When canceled, cancels all running processes. */ @Type(DEVICE_TARGET) -public interface Device : AutoCloseable, ContextAware, CoroutineScope { +public interface Device : ContextAware, CoroutineScope { /** * Initial configuration meta for the device */ public val meta: Meta get() = Meta.EMPTY + /** * List of supported property descriptors */ @@ -87,12 +99,12 @@ public interface Device : AutoCloseable, ContextAware, CoroutineScope { /** * Initialize the device. This function suspends until the device is finished initialization */ - public suspend fun open(): Unit = Unit + public suspend fun start(): Unit = Unit /** * Close and terminate the device. This function does not wait for the device to be closed. */ - override fun close() { + public fun stop() { logger.info { "Device $this is closed" } cancel("The device is closed") } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt index 45d7f0b..c59e4c3 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt @@ -25,7 +25,7 @@ public sealed class DeviceMessage { public abstract val time: Instant? /** - * Update the source device name for composition. If the original name is null, resulting name is also null. + * Update the source device name for composition. If the original name is null, the resulting name is also null. */ public abstract fun changeSource(block: (Name) -> Name): DeviceMessage @@ -203,12 +203,12 @@ public data class EmptyDeviceMessage( public data class DeviceLogMessage( val message: String, val data: Meta? = null, - override val sourceDevice: Name? = null, + override val sourceDevice: Name = Name.EMPTY, override val targetDevice: Name? = null, override val comment: String? = null, @EncodeDefault override val time: Instant? = Clock.System.now(), ) : DeviceMessage() { - override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice)) } /** @@ -220,7 +220,7 @@ public data class DeviceErrorMessage( public val errorMessage: String?, public val errorType: String? = null, public val errorStackTrace: String? = null, - override val sourceDevice: Name, + override val sourceDevice: Name = Name.EMPTY, override val targetDevice: Name? = null, override val comment: String? = null, @EncodeDefault override val time: Instant? = Clock.System.now(), diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/descriptors.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/descriptors.kt index 8e1705b..6f7d27c 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/descriptors.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/descriptors.kt @@ -12,10 +12,10 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder @Serializable public class PropertyDescriptor( public val name: String, - public var info: String? = null, + public var description: String? = null, public var metaDescriptor: MetaDescriptor = MetaDescriptor(), public var readable: Boolean = true, - public var writable: Boolean = false + public var mutable: Boolean = false ) public fun PropertyDescriptor.metaDescriptor(block: MetaDescriptorBuilder.()->Unit){ diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt index cc043c7..be59b4c 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt @@ -40,7 +40,7 @@ public class DeviceManager : AbstractPlugin(), DeviceHub { public fun DeviceManager.install(name: String, device: D): D { registerDevice(NameToken(name), device) device.launch { - device.open() + device.start() } return device } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt index 38aa20a..b1544e0 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt @@ -1,12 +1,9 @@ package space.kscience.controls.spec -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.* import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.newCoroutineContext import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import space.kscience.controls.api.* @@ -69,8 +66,28 @@ public abstract class DeviceBase( override val actionDescriptors: Collection get() = actions.values.map { it.descriptor } + + private val sharedMessageFlow: MutableSharedFlow = MutableSharedFlow( + replay = meta["message.buffer"].int ?: 1000, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + override val coroutineContext: CoroutineContext by lazy { - context.newCoroutineContext(SupervisorJob(context.coroutineContext[Job]) + CoroutineName("Device $this")) + context.newCoroutineContext( + SupervisorJob(context.coroutineContext[Job]) + + CoroutineName("Device $this") + + CoroutineExceptionHandler { _, throwable -> + launch { + sharedMessageFlow.emit( + DeviceErrorMessage( + errorMessage = throwable.message, + errorType = throwable::class.simpleName, + errorStackTrace = throwable.stackTraceToString() + ) + ) + } + } + ) } @@ -79,11 +96,6 @@ public abstract class DeviceBase( */ private val logicalState: HashMap = HashMap() - private val sharedMessageFlow: MutableSharedFlow = MutableSharedFlow( - replay = meta["message.buffer"].int ?: 1000, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) - public override val messageFlow: SharedFlow get() = sharedMessageFlow @Suppress("UNCHECKED_CAST") @@ -180,18 +192,30 @@ public abstract class DeviceBase( override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.INIT protected set - @OptIn(DFExperimental::class) - override suspend fun open() { - super.open() - lifecycleState = DeviceLifecycleState.OPEN + protected open suspend fun onStart() { + } @OptIn(DFExperimental::class) - override fun close() { - lifecycleState = DeviceLifecycleState.CLOSED - super.close() + final override suspend fun start() { + super.start() + lifecycleState = DeviceLifecycleState.INIT + onStart() + lifecycleState = DeviceLifecycleState.OPEN } + protected open fun onStop() { + + } + + @OptIn(DFExperimental::class) + final override fun stop() { + onStop() + lifecycleState = DeviceLifecycleState.CLOSED + super.stop() + } + + abstract override fun toString(): String } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBySpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBySpec.kt index 9309224..31f4c09 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBySpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBySpec.kt @@ -16,15 +16,14 @@ public open class DeviceBySpec( override val properties: Map> get() = spec.properties override val actions: Map> get() = spec.actions - override suspend fun open(): Unit = with(spec) { - super.open() + override suspend fun onStart(): Unit = with(spec) { self.onOpen() } - override fun close(): Unit = with(spec) { + override fun onStop(): Unit = with(spec){ self.onClose() - super.close() } + override fun toString(): String = "Device(spec=$spec)" } \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt index 7f3aa06..c42a23e 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt @@ -20,7 +20,7 @@ public annotation class InternalDeviceAPI /** * Specification for a device read-only property */ -public interface DevicePropertySpec { +public interface DevicePropertySpec { /** * Property descriptor */ @@ -53,7 +53,7 @@ public interface WritableDevicePropertySpec : DevicePropertySp } -public interface DeviceActionSpec { +public interface DeviceActionSpec { /** * Action descriptor */ diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt index 0376635..14966ca 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt @@ -53,7 +53,7 @@ public abstract class DeviceSpec { val deviceProperty = object : DevicePropertySpec { override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply { //TODO add type from converter - writable = true + mutable = true }.apply(descriptorBuilder) override val converter: MetaConverter = converter @@ -78,7 +78,7 @@ public abstract class DeviceSpec { override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply { //TODO add the type from converter - writable = true + mutable = true }.apply(descriptorBuilder) override val converter: MetaConverter = converter @@ -127,7 +127,7 @@ public abstract class DeviceSpec { PropertyDelegateProvider { _: DeviceSpec, property: KProperty<*> -> val propertyName = name ?: property.name val deviceProperty = object : WritableDevicePropertySpec { - override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName, writable = true) + override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName, mutable = true) .apply(descriptorBuilder) override val converter: MetaConverter = converter @@ -224,7 +224,7 @@ public fun > DeviceSpec.logicalProperty( val propertyName = name ?: property.name override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply { //TODO add type from converter - writable = true + mutable = true }.apply(descriptorBuilder) override val converter: MetaConverter = converter diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceTree.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceTree.kt new file mode 100644 index 0000000..f100af6 --- /dev/null +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceTree.kt @@ -0,0 +1,36 @@ +package space.kscience.controls.spec + +import space.kscience.controls.api.Device +import space.kscience.controls.api.DeviceHub +import space.kscience.controls.manager.DeviceManager +import space.kscience.dataforge.context.Factory +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.names.NameToken +import kotlin.collections.Map +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.mapValues +import kotlin.collections.mutableMapOf +import kotlin.collections.set + + +public class DeviceTree( + public val deviceManager: DeviceManager, + public val meta: Meta, + builder: Builder, +) : DeviceHub { + public class Builder(public val manager: DeviceManager) { + internal val childrenFactories = mutableMapOf>() + + public fun device(name: String, factory: Factory) { + childrenFactories[NameToken.parse(name)] = factory + } + } + + override val devices: Map = builder.childrenFactories.mapValues { (token, factory) -> + val devicesMeta = meta["devices"] + factory.build(deviceManager.context, devicesMeta?.get(token) ?: Meta.EMPTY) + } + +} \ No newline at end of file diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt index 32adfb3..b4fae90 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt @@ -237,7 +237,7 @@ public fun D.bindProcessImage( image.setLocked(true) if (openOnBind) { launch { - open() + start() } } return image diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt index 48f48a4..995e0df 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt @@ -20,16 +20,14 @@ public open class ModbusDeviceBySpec( private val disposeMasterOnClose: Boolean = true, meta: Meta = Meta.EMPTY, ) : ModbusDevice, DeviceBySpec(spec, context, meta){ - override suspend fun open() { + override suspend fun onStart() { master.connect() - super.open() } - override fun close() { + override fun onStop() { if(disposeMasterOnClose){ master.disconnect() } - super.close() } } diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/OpcUaDeviceBySpec.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/OpcUaDeviceBySpec.kt index eb9b688..188760e 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/OpcUaDeviceBySpec.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/OpcUaDeviceBySpec.kt @@ -63,8 +63,7 @@ public open class OpcUaDeviceBySpec( } } - override fun close() { + override fun onStop() { client.disconnect() - super.close() } } diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt index 010c2c0..a05a81d 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt @@ -73,11 +73,11 @@ public class DeviceNameSpace( //for now, use DF paths as ids nodeId = newNodeId("${deviceName.tokens.joinToString("/")}/$propertyName") when { - descriptor.readable && descriptor.writable -> { + descriptor.readable && descriptor.mutable -> { setAccessLevel(AccessLevel.READ_WRITE) setUserAccessLevel(AccessLevel.READ_WRITE) } - descriptor.writable -> { + descriptor.mutable -> { setAccessLevel(AccessLevel.WRITE_ONLY) setUserAccessLevel(AccessLevel.WRITE_ONLY) } diff --git a/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt b/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt index eedafe7..8537449 100644 --- a/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt +++ b/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt @@ -40,9 +40,10 @@ class OpcUaClientTest { @Test @Ignore fun testReadDouble() = runTest { - DemoOpcUaDevice.build().use{ - println(it.read(DemoOpcUaDevice.randomDouble)) - } + val device = DemoOpcUaDevice.build() + device.start() + println(device.read(DemoOpcUaDevice.randomDouble)) + device.stop() } } \ No newline at end of file diff --git a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt index 94815fa..8edd90e 100644 --- a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt +++ b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt @@ -78,7 +78,7 @@ class DemoController : Controller(), ContextAware { logger.info { "Visualization server stopped" } magixServer?.stop(1000, 5000) logger.info { "Magix server stopped" } - device?.close() + device?.stop() logger.info { "Device server stopped" } context.close() } diff --git a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt index dd65b78..4f902c0 100644 --- a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt +++ b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt @@ -44,7 +44,7 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec(Compa metaDescriptor { type(ValueType.NUMBER) } - info = "Real to virtual time scale" + description = "Real to virtual time scale" } val sinScale by mutableProperty(MetaConverter.double, IDemoDevice::sinScaleState) diff --git a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/MagixVirtualCar.kt b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/MagixVirtualCar.kt index 03c781b..2c5d65a 100644 --- a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/MagixVirtualCar.kt +++ b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/MagixVirtualCar.kt @@ -14,7 +14,6 @@ import space.kscience.dataforge.names.Name import space.kscience.magix.api.MagixEndpoint import space.kscience.magix.api.subscribe import space.kscience.magix.rsocket.rSocketWithWebSockets -import kotlin.time.ExperimentalTime class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta) { @@ -31,17 +30,13 @@ class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta) } - @OptIn(ExperimentalTime::class) - override suspend fun open() { - super.open() + override suspend fun onStart() { val magixEndpoint = MagixEndpoint.rSocketWithWebSockets( meta["magixServerHost"].string ?: "localhost", ) - launch { - magixEndpoint.launchMagixVirtualCarUpdate() - } + magixEndpoint.launchMagixVirtualCarUpdate() } companion object : Factory { diff --git a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCar.kt b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCar.kt index 1f1dc69..2ba0fdb 100644 --- a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCar.kt +++ b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCar.kt @@ -100,8 +100,7 @@ open class VirtualCar(context: Context, meta: Meta) : DeviceBySpec(I } @OptIn(ExperimentalTime::class) - override suspend fun open() { - super.open() + override suspend fun onStart() { //initializing the clock timeState = Clock.System.now() //starting regular updates diff --git a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCarController.kt b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCarController.kt index 7a170ac..3a63003 100644 --- a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCarController.kt +++ b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCarController.kt @@ -71,9 +71,9 @@ class VirtualCarController : Controller(), ContextAware { logger.info { "Shutting down..." } magixServer?.stop(1000, 5000) logger.info { "Magix server stopped" } - magixVirtualCar?.close() + magixVirtualCar?.stop() logger.info { "Magix virtual car server stopped" } - virtualCar?.close() + virtualCar?.stop() logger.info { "Virtual car server stopped" } context.close() } diff --git a/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt b/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt index 54573e4..18e5838 100644 --- a/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt +++ b/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt @@ -138,7 +138,7 @@ class PiMotionMasterDevice( override fun build(context: Context, meta: Meta): PiMotionMasterDevice = PiMotionMasterDevice(context) val connected by booleanProperty(descriptorBuilder = { - info = "True if the connection address is defined and the device is initialized" + description = "True if the connection address is defined and the device is initialized" }) { port != null } @@ -201,7 +201,7 @@ class PiMotionMasterDevice( val timeout by mutableProperty(MetaConverter.duration, PiMotionMasterDevice::timeoutValue) { - info = "Timeout" + description = "Timeout" } } @@ -267,7 +267,7 @@ class PiMotionMasterDevice( ) val enabled by axisBooleanProperty("EAX") { - info = "Motor enable state." + description = "Motor enable state." } val halt by unitAction { @@ -275,20 +275,20 @@ class PiMotionMasterDevice( } val targetPosition by axisNumberProperty("MOV") { - info = """ + description = """ Sets a new absolute target position for the specified axis. Servo mode must be switched on for the commanded axis prior to using this command (closed-loop operation). """.trimIndent() } val onTarget by booleanProperty({ - info = "Queries the on-target state of the specified axis." + description = "Queries the on-target state of the specified axis." }) { readAxisBoolean("ONT?") } val reference by booleanProperty({ - info = "Get Referencing Result" + description = "Get Referencing Result" }) { readAxisBoolean("FRF?") } @@ -298,36 +298,36 @@ class PiMotionMasterDevice( } val minPosition by doubleProperty({ - info = "Minimal position value for the axis" + description = "Minimal position value for the axis" }) { mm.requestAndParse("TMN?", axisId)[axisId]?.toDoubleOrNull() ?: error("Malformed `TMN?` response. Should include float value for $axisId") } val maxPosition by doubleProperty({ - info = "Maximal position value for the axis" + description = "Maximal position value for the axis" }) { mm.requestAndParse("TMX?", axisId)[axisId]?.toDoubleOrNull() ?: error("Malformed `TMX?` response. Should include float value for $axisId") } val position by doubleProperty({ - info = "The current axis position." + description = "The current axis position." }) { mm.requestAndParse("POS?", axisId)[axisId]?.toDoubleOrNull() ?: error("Malformed `POS?` response. Should include float value for $axisId") } val openLoopTarget by axisNumberProperty("OMA") { - info = "Position for open-loop operation." + description = "Position for open-loop operation." } val closedLoop by axisBooleanProperty("SVO") { - info = "Servo closed loop mode" + description = "Servo closed loop mode" } val velocity by axisNumberProperty("VEL") { - info = "Velocity value for closed-loop operation" + description = "Velocity value for closed-loop operation" } val move by action(MetaConverter.meta, MetaConverter.unit) { diff --git a/gradle.properties b/gradle.properties index c7e0d88..62d03f6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ publishing.sonatype=false org.gradle.configureondemand=true org.gradle.jvmargs=-Xmx4096m -toolsVersion=0.15.0-kotlin-1.9.20-RC \ No newline at end of file +toolsVersion=0.15.0-kotlin-1.9.20-RC2 \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 20ac44e..0f53909 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -50,6 +50,7 @@ include( // ":controls-mongo", ":controls-storage", ":controls-storage:controls-xodus", + ":controls-constructor", ":magix", ":magix:magix-api", ":magix:magix-server",