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 9d6e9ca..6298657 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 @@ -44,6 +44,8 @@ public interface Device : Closeable, ContextAware, CoroutineScope { /** * Invalidate property (set logical state to invalid) + * + * This message is suspended to provide lock-free local property changes (they require coroutine context). */ public suspend fun invalidate(propertyName: String) @@ -77,11 +79,13 @@ public interface Device : Closeable, ContextAware, CoroutineScope { /** * Get the logical state of property or suspend to read the physical value. */ -public suspend fun Device.getOrReadItem(propertyName: String): Meta = +public suspend fun Device.getOrReadProperty(propertyName: String): Meta = getProperty(propertyName) ?: readProperty(propertyName) /** * Get a snapshot of logical state of the device + * + * TODO currently this */ public fun Device.getProperties(): Meta = Meta { for (descriptor in propertyDescriptors) { @@ -93,7 +97,4 @@ public fun Device.getProperties(): Meta = Meta { * Subscribe on property changes for the whole device */ public fun Device.onPropertyChange(callback: suspend PropertyChangedMessage.() -> Unit): Job = - messageFlow.filterIsInstance().onEach(callback).launchIn(this) - - -//public suspend fun Device.execute(name: String, meta: Meta?): Meta? = execute(name, meta?.let { MetaNode(it) }) \ No newline at end of file + messageFlow.filterIsInstance().onEach(callback).launchIn(this) \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/deviceMessages.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/deviceMessages.kt index 0480669..5158961 100644 --- a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/deviceMessages.kt +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/controllers/deviceMessages.kt @@ -18,7 +18,7 @@ public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMess is PropertyGetMessage -> { PropertyChangedMessage( property = request.property, - value = getOrReadItem(request.property), + value = getOrReadProperty(request.property), sourceDevice = deviceTarget, targetDevice = request.sourceDevice ) @@ -32,7 +32,7 @@ public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMess } PropertyChangedMessage( property = request.property, - value = getOrReadItem(request.property), + value = getOrReadProperty(request.property), sourceDevice = deviceTarget, targetDevice = request.sourceDevice ) 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 2d8ce2a..c569cc3 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 @@ -54,6 +54,9 @@ public open class DeviceBySpec>( private val stateLock = Mutex() + /** + * Update logical property state and notify listeners + */ protected suspend fun updateLogical(propertyName: String, value: Meta?) { if (value != logicalState[propertyName]) { stateLock.withLock { @@ -87,8 +90,8 @@ public open class DeviceBySpec>( override suspend fun writeProperty(propertyName: String, value: Meta): Unit { //If there is a physical property with given name, invalidate logical property and write physical one (properties[propertyName] as? WritableDevicePropertySpec)?.let { - it.writeMeta(self, value) invalidate(propertyName) + it.writeMeta(self, value) } ?: run { updateLogical(propertyName, value) } @@ -112,8 +115,12 @@ public open class DeviceBySpec>( * Write typed property state and invalidate logical state */ public suspend fun WritableDevicePropertySpec.write(value: T) { - write(self, value) invalidate(name) + write(self, value) + //perform asynchronous read and update after write + launch { + read() + } } override fun close() { 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 a1405c9..934220f 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 @@ -51,16 +51,19 @@ public abstract class DeviceSpec>( PropertyDelegateProvider { _, property -> val deviceProperty = object : WritableDevicePropertySpec { override val name: String = property.name + override val descriptor: PropertyDescriptor = PropertyDescriptor(name).apply { //TODO add type from converter writable = true }.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) { + override suspend fun write(device: D, value: T): Unit = withContext(device.coroutineContext) { readWriteProperty.set(device, value) } } @@ -107,7 +110,7 @@ public abstract class DeviceSpec>( override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() } - override suspend fun write(device: D, value: T) = withContext(device.coroutineContext) { + override suspend fun write(device: D, value: T): Unit = withContext(device.coroutineContext) { device.write(value) } } diff --git a/controls-magix-client/src/commonMain/kotlin/ru/mipt/npm/controls/client/tangoMagix.kt b/controls-magix-client/src/commonMain/kotlin/ru/mipt/npm/controls/client/tangoMagix.kt index 239089b..922098a 100644 --- a/controls-magix-client/src/commonMain/kotlin/ru/mipt/npm/controls/client/tangoMagix.kt +++ b/controls-magix-client/src/commonMain/kotlin/ru/mipt/npm/controls/client/tangoMagix.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import ru.mipt.npm.controls.api.get -import ru.mipt.npm.controls.api.getOrReadItem +import ru.mipt.npm.controls.api.getOrReadProperty import ru.mipt.npm.controls.controllers.DeviceManager import ru.mipt.npm.magix.api.MagixEndpoint import ru.mipt.npm.magix.api.MagixMessage @@ -84,7 +84,7 @@ public fun DeviceManager.launchTangoMagix( val device = get(request.payload.device) when (request.payload.action) { TangoAction.read -> { - val value = device.getOrReadItem(request.payload.name) + val value = device.getOrReadProperty(request.payload.name) respond(request) { requestPayload -> requestPayload.copy( value = value, @@ -97,7 +97,7 @@ public fun DeviceManager.launchTangoMagix( device.writeProperty(request.payload.name, value) } //wait for value to be written and return final state - val value = device.getOrReadItem(request.payload.name) + val value = device.getOrReadProperty(request.payload.name) respond(request) { requestPayload -> requestPayload.copy( value = value, diff --git a/controls-opcua/src/main/kotlin/ru/mipt/npm/controls/opcua/server/DeviceNameSpace.kt b/controls-opcua/src/main/kotlin/ru/mipt/npm/controls/opcua/server/DeviceNameSpace.kt index 5c4a6f5..e187f5e 100644 --- a/controls-opcua/src/main/kotlin/ru/mipt/npm/controls/opcua/server/DeviceNameSpace.kt +++ b/controls-opcua/src/main/kotlin/ru/mipt/npm/controls/opcua/server/DeviceNameSpace.kt @@ -1,6 +1,7 @@ package ru.mipt.npm.controls.opcua.server import kotlinx.coroutines.launch +import kotlinx.datetime.toJavaInstant import kotlinx.serialization.json.Json import org.eclipse.milo.opcua.sdk.core.AccessLevel import org.eclipse.milo.opcua.sdk.core.Reference @@ -15,6 +16,7 @@ import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel import org.eclipse.milo.opcua.stack.core.AttributeId import org.eclipse.milo.opcua.stack.core.Identifiers +import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText import ru.mipt.npm.controls.api.Device import ru.mipt.npm.controls.api.DeviceHub @@ -123,14 +125,14 @@ public class DeviceNameSpace( }.build() - device[descriptor]?.toOpc()?.let { + device[descriptor]?.toOpc(sourceTime = null, serverTime = null)?.let { node.value = it } /** * Subscribe to node value changes */ - node.addAttributeObserver { uaNode: UaNode, attributeId: AttributeId, value: Any -> + node.addAttributeObserver { _: UaNode, attributeId: AttributeId, value: Any -> if (attributeId == AttributeId.Value) { val meta: Meta = when (value) { is Meta -> value @@ -153,7 +155,8 @@ public class DeviceNameSpace( //Subscribe on properties updates device.onPropertyChange { nodes[property]?.let { node -> - node.value = value.toOpc() + val sourceTime = time?.let { DateTime(it.toJavaInstant()) } + node.value = value.toOpc(sourceTime = sourceTime) } } //recursively add sub-devices diff --git a/controls-opcua/src/main/kotlin/ru/mipt/npm/controls/opcua/server/metaToOpc.kt b/controls-opcua/src/main/kotlin/ru/mipt/npm/controls/opcua/server/metaToOpc.kt index 7aab6aa..cbcd2ec 100644 --- a/controls-opcua/src/main/kotlin/ru/mipt/npm/controls/opcua/server/metaToOpc.kt +++ b/controls-opcua/src/main/kotlin/ru/mipt/npm/controls/opcua/server/metaToOpc.kt @@ -1,12 +1,12 @@ package ru.mipt.npm.controls.opcua.server -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode import org.eclipse.milo.opcua.stack.core.types.builtin.Variant import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.MetaSerializer import space.kscience.dataforge.meta.isLeaf import space.kscience.dataforge.values.* import java.time.Instant @@ -32,7 +32,7 @@ internal fun Meta.toOpc( } } } else { - Variant(Json.encodeToString(this)) + Variant(Json.encodeToString(MetaSerializer,this)) } return DataValue(variant, statusCode, sourceTime,serverTime ?: DateTime(Instant.now())) } \ 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 7fa6252..8a273ab 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 @@ -99,7 +99,7 @@ class DemoControllerView : View(title = " Demo controller remote") { pane { hgrow = Priority.ALWAYS } - xScaleSlider = slider(0.0..2.0, 1.0) { + xScaleSlider = slider(0.1..2.0, 1.0) { isShowTickLabels = true isShowTickMarks = true } @@ -109,7 +109,7 @@ class DemoControllerView : View(title = " Demo controller remote") { pane { hgrow = Priority.ALWAYS } - yScaleSlider = slider(0.0..2.0, 1.0) { + yScaleSlider = slider(0.1..2.0, 1.0) { isShowTickLabels = true isShowTickMarks = true } 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 42a7bd8..7882cbc 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,5 +1,6 @@ package ru.mipt.npm.controls.demo +import kotlinx.coroutines.launch import ru.mipt.npm.controls.properties.* import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.transformations.MetaConverter @@ -47,9 +48,12 @@ class DemoDevice : DeviceBySpec(DemoDevice) { @OptIn(ExperimentalTime::class) override fun DemoDevice.onStartup() { + launch { + sinScale.read() + cosScale.read() + } doRecurring(Duration.milliseconds(50)){ - sin.read() - cos.read() + coordinates.read() } } }