Add proper read after write for properties
This commit is contained in:
parent
b3898d2ab3
commit
7ae75d8bd4
@ -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) {
|
||||
@ -94,6 +98,3 @@ public fun Device.getProperties(): Meta = Meta {
|
||||
*/
|
||||
public fun Device.onPropertyChange(callback: suspend PropertyChangedMessage.() -> Unit): Job =
|
||||
messageFlow.filterIsInstance<PropertyChangedMessage>().onEach(callback).launchIn(this)
|
||||
|
||||
|
||||
//public suspend fun Device.execute(name: String, meta: Meta?): Meta? = execute(name, meta?.let { MetaNode(it) })
|
@ -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
|
||||
)
|
||||
|
@ -54,6 +54,9 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||
|
||||
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<D : DeviceBySpec<D>>(
|
||||
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<D, out Any?>)?.let {
|
||||
it.writeMeta(self, value)
|
||||
invalidate(propertyName)
|
||||
it.writeMeta(self, value)
|
||||
} ?: run {
|
||||
updateLogical(propertyName, value)
|
||||
}
|
||||
@ -112,8 +115,12 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||
* Write typed property state and invalidate logical state
|
||||
*/
|
||||
public suspend fun <T> WritableDevicePropertySpec<D, T>.write(value: T) {
|
||||
write(self, value)
|
||||
invalidate(name)
|
||||
write(self, value)
|
||||
//perform asynchronous read and update after write
|
||||
launch {
|
||||
read()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
|
@ -51,16 +51,19 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
||||
PropertyDelegateProvider { _, property ->
|
||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||
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<T> = 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<D : DeviceBySpec<D>>(
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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()))
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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>(DemoDevice) {
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
override fun DemoDevice.onStartup() {
|
||||
launch {
|
||||
sinScale.read()
|
||||
cosScale.read()
|
||||
}
|
||||
doRecurring(Duration.milliseconds(50)){
|
||||
sin.read()
|
||||
cos.read()
|
||||
coordinates.read()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user