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)
|
* 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)
|
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.
|
* 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)
|
getProperty(propertyName) ?: readProperty(propertyName)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a snapshot of logical state of the device
|
* Get a snapshot of logical state of the device
|
||||||
|
*
|
||||||
|
* TODO currently this
|
||||||
*/
|
*/
|
||||||
public fun Device.getProperties(): Meta = Meta {
|
public fun Device.getProperties(): Meta = Meta {
|
||||||
for (descriptor in propertyDescriptors) {
|
for (descriptor in propertyDescriptors) {
|
||||||
@ -93,7 +97,4 @@ public fun Device.getProperties(): Meta = Meta {
|
|||||||
* Subscribe on property changes for the whole device
|
* Subscribe on property changes for the whole device
|
||||||
*/
|
*/
|
||||||
public fun Device.onPropertyChange(callback: suspend PropertyChangedMessage.() -> Unit): Job =
|
public fun Device.onPropertyChange(callback: suspend PropertyChangedMessage.() -> Unit): Job =
|
||||||
messageFlow.filterIsInstance<PropertyChangedMessage>().onEach(callback).launchIn(this)
|
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 -> {
|
is PropertyGetMessage -> {
|
||||||
PropertyChangedMessage(
|
PropertyChangedMessage(
|
||||||
property = request.property,
|
property = request.property,
|
||||||
value = getOrReadItem(request.property),
|
value = getOrReadProperty(request.property),
|
||||||
sourceDevice = deviceTarget,
|
sourceDevice = deviceTarget,
|
||||||
targetDevice = request.sourceDevice
|
targetDevice = request.sourceDevice
|
||||||
)
|
)
|
||||||
@ -32,7 +32,7 @@ public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMess
|
|||||||
}
|
}
|
||||||
PropertyChangedMessage(
|
PropertyChangedMessage(
|
||||||
property = request.property,
|
property = request.property,
|
||||||
value = getOrReadItem(request.property),
|
value = getOrReadProperty(request.property),
|
||||||
sourceDevice = deviceTarget,
|
sourceDevice = deviceTarget,
|
||||||
targetDevice = request.sourceDevice
|
targetDevice = request.sourceDevice
|
||||||
)
|
)
|
||||||
|
@ -54,6 +54,9 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
|
|
||||||
private val stateLock = Mutex()
|
private val stateLock = Mutex()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update logical property state and notify listeners
|
||||||
|
*/
|
||||||
protected suspend fun updateLogical(propertyName: String, value: Meta?) {
|
protected suspend fun updateLogical(propertyName: String, value: Meta?) {
|
||||||
if (value != logicalState[propertyName]) {
|
if (value != logicalState[propertyName]) {
|
||||||
stateLock.withLock {
|
stateLock.withLock {
|
||||||
@ -87,8 +90,8 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
override suspend fun writeProperty(propertyName: String, value: Meta): Unit {
|
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
|
//If there is a physical property with given name, invalidate logical property and write physical one
|
||||||
(properties[propertyName] as? WritableDevicePropertySpec<D, out Any?>)?.let {
|
(properties[propertyName] as? WritableDevicePropertySpec<D, out Any?>)?.let {
|
||||||
it.writeMeta(self, value)
|
|
||||||
invalidate(propertyName)
|
invalidate(propertyName)
|
||||||
|
it.writeMeta(self, value)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
updateLogical(propertyName, value)
|
updateLogical(propertyName, value)
|
||||||
}
|
}
|
||||||
@ -112,8 +115,12 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
* Write typed property state and invalidate logical state
|
* Write typed property state and invalidate logical state
|
||||||
*/
|
*/
|
||||||
public suspend fun <T> WritableDevicePropertySpec<D, T>.write(value: T) {
|
public suspend fun <T> WritableDevicePropertySpec<D, T>.write(value: T) {
|
||||||
write(self, value)
|
|
||||||
invalidate(name)
|
invalidate(name)
|
||||||
|
write(self, value)
|
||||||
|
//perform asynchronous read and update after write
|
||||||
|
launch {
|
||||||
|
read()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
@ -51,16 +51,19 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
PropertyDelegateProvider { _, property ->
|
PropertyDelegateProvider { _, property ->
|
||||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||||
override val name: String = property.name
|
override val name: String = property.name
|
||||||
|
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(name).apply {
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(name).apply {
|
||||||
//TODO add type from converter
|
//TODO add type from converter
|
||||||
writable = true
|
writable = true
|
||||||
}.apply(descriptorBuilder)
|
}.apply(descriptorBuilder)
|
||||||
|
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
|
|
||||||
override suspend fun read(device: D): T = withContext(device.coroutineContext) {
|
override suspend fun read(device: D): T = withContext(device.coroutineContext) {
|
||||||
readWriteProperty.get(device)
|
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)
|
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 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)
|
device.write(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import ru.mipt.npm.controls.api.get
|
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.controls.controllers.DeviceManager
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||||
import ru.mipt.npm.magix.api.MagixMessage
|
import ru.mipt.npm.magix.api.MagixMessage
|
||||||
@ -84,7 +84,7 @@ public fun DeviceManager.launchTangoMagix(
|
|||||||
val device = get(request.payload.device)
|
val device = get(request.payload.device)
|
||||||
when (request.payload.action) {
|
when (request.payload.action) {
|
||||||
TangoAction.read -> {
|
TangoAction.read -> {
|
||||||
val value = device.getOrReadItem(request.payload.name)
|
val value = device.getOrReadProperty(request.payload.name)
|
||||||
respond(request) { requestPayload ->
|
respond(request) { requestPayload ->
|
||||||
requestPayload.copy(
|
requestPayload.copy(
|
||||||
value = value,
|
value = value,
|
||||||
@ -97,7 +97,7 @@ public fun DeviceManager.launchTangoMagix(
|
|||||||
device.writeProperty(request.payload.name, value)
|
device.writeProperty(request.payload.name, value)
|
||||||
}
|
}
|
||||||
//wait for value to be written and return final state
|
//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 ->
|
respond(request) { requestPayload ->
|
||||||
requestPayload.copy(
|
requestPayload.copy(
|
||||||
value = value,
|
value = value,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.mipt.npm.controls.opcua.server
|
package ru.mipt.npm.controls.opcua.server
|
||||||
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.datetime.toJavaInstant
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.eclipse.milo.opcua.sdk.core.AccessLevel
|
import org.eclipse.milo.opcua.sdk.core.AccessLevel
|
||||||
import org.eclipse.milo.opcua.sdk.core.Reference
|
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.sdk.server.util.SubscriptionModel
|
||||||
import org.eclipse.milo.opcua.stack.core.AttributeId
|
import org.eclipse.milo.opcua.stack.core.AttributeId
|
||||||
import org.eclipse.milo.opcua.stack.core.Identifiers
|
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 org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText
|
||||||
import ru.mipt.npm.controls.api.Device
|
import ru.mipt.npm.controls.api.Device
|
||||||
import ru.mipt.npm.controls.api.DeviceHub
|
import ru.mipt.npm.controls.api.DeviceHub
|
||||||
@ -123,14 +125,14 @@ public class DeviceNameSpace(
|
|||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
|
|
||||||
device[descriptor]?.toOpc()?.let {
|
device[descriptor]?.toOpc(sourceTime = null, serverTime = null)?.let {
|
||||||
node.value = it
|
node.value = it
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to node value changes
|
* Subscribe to node value changes
|
||||||
*/
|
*/
|
||||||
node.addAttributeObserver { uaNode: UaNode, attributeId: AttributeId, value: Any ->
|
node.addAttributeObserver { _: UaNode, attributeId: AttributeId, value: Any ->
|
||||||
if (attributeId == AttributeId.Value) {
|
if (attributeId == AttributeId.Value) {
|
||||||
val meta: Meta = when (value) {
|
val meta: Meta = when (value) {
|
||||||
is Meta -> value
|
is Meta -> value
|
||||||
@ -153,7 +155,8 @@ public class DeviceNameSpace(
|
|||||||
//Subscribe on properties updates
|
//Subscribe on properties updates
|
||||||
device.onPropertyChange {
|
device.onPropertyChange {
|
||||||
nodes[property]?.let { node ->
|
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
|
//recursively add sub-devices
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package ru.mipt.npm.controls.opcua.server
|
package ru.mipt.npm.controls.opcua.server
|
||||||
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue
|
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.DateTime
|
||||||
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode
|
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode
|
||||||
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant
|
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.MetaSerializer
|
||||||
import space.kscience.dataforge.meta.isLeaf
|
import space.kscience.dataforge.meta.isLeaf
|
||||||
import space.kscience.dataforge.values.*
|
import space.kscience.dataforge.values.*
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@ -32,7 +32,7 @@ internal fun Meta.toOpc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Variant(Json.encodeToString(this))
|
Variant(Json.encodeToString(MetaSerializer,this))
|
||||||
}
|
}
|
||||||
return DataValue(variant, statusCode, sourceTime,serverTime ?: DateTime(Instant.now()))
|
return DataValue(variant, statusCode, sourceTime,serverTime ?: DateTime(Instant.now()))
|
||||||
}
|
}
|
@ -99,7 +99,7 @@ class DemoControllerView : View(title = " Demo controller remote") {
|
|||||||
pane {
|
pane {
|
||||||
hgrow = Priority.ALWAYS
|
hgrow = Priority.ALWAYS
|
||||||
}
|
}
|
||||||
xScaleSlider = slider(0.0..2.0, 1.0) {
|
xScaleSlider = slider(0.1..2.0, 1.0) {
|
||||||
isShowTickLabels = true
|
isShowTickLabels = true
|
||||||
isShowTickMarks = true
|
isShowTickMarks = true
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ class DemoControllerView : View(title = " Demo controller remote") {
|
|||||||
pane {
|
pane {
|
||||||
hgrow = Priority.ALWAYS
|
hgrow = Priority.ALWAYS
|
||||||
}
|
}
|
||||||
yScaleSlider = slider(0.0..2.0, 1.0) {
|
yScaleSlider = slider(0.1..2.0, 1.0) {
|
||||||
isShowTickLabels = true
|
isShowTickLabels = true
|
||||||
isShowTickMarks = true
|
isShowTickMarks = true
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.mipt.npm.controls.demo
|
package ru.mipt.npm.controls.demo
|
||||||
|
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.controls.properties.*
|
import ru.mipt.npm.controls.properties.*
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
@ -47,9 +48,12 @@ class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
|||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
@OptIn(ExperimentalTime::class)
|
||||||
override fun DemoDevice.onStartup() {
|
override fun DemoDevice.onStartup() {
|
||||||
|
launch {
|
||||||
|
sinScale.read()
|
||||||
|
cosScale.read()
|
||||||
|
}
|
||||||
doRecurring(Duration.milliseconds(50)){
|
doRecurring(Duration.milliseconds(50)){
|
||||||
sin.read()
|
coordinates.read()
|
||||||
cos.read()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user