Add proper read after write for properties

This commit is contained in:
Alexander Nozik 2021-10-11 22:55:15 +03:00
parent b3898d2ab3
commit 7ae75d8bd4
9 changed files with 41 additions and 23 deletions

View File

@ -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<PropertyChangedMessage>().onEach(callback).launchIn(this)
//public suspend fun Device.execute(name: String, meta: Meta?): Meta? = execute(name, meta?.let { MetaNode(it) })
messageFlow.filterIsInstance<PropertyChangedMessage>().onEach(callback).launchIn(this)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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