Cleanup DeviceWithSpec API. Now all public things are in Spec
This commit is contained in:
parent
d503f0499e
commit
3aa00ec491
@ -47,7 +47,7 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
|||||||
* Set property [value] for a property with name [propertyName].
|
* Set property [value] for a property with name [propertyName].
|
||||||
* In rare cases could suspend if the [Device] supports command queue and it is full at the moment.
|
* In rare cases could suspend if the [Device] supports command queue and it is full at the moment.
|
||||||
*/
|
*/
|
||||||
public suspend fun writeItem(propertyName: String, value: Meta)
|
public suspend fun writeProperty(propertyName: String, value: Meta)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A subscription-based [Flow] of [DeviceMessage] provided by device. The flow is guaranteed to be readable
|
* A subscription-based [Flow] of [DeviceMessage] provided by device. The flow is guaranteed to be readable
|
||||||
|
@ -59,8 +59,8 @@ public operator fun DeviceHub.get(nameString: String): Device =
|
|||||||
public suspend fun DeviceHub.readProperty(deviceName: Name, propertyName: String): Meta =
|
public suspend fun DeviceHub.readProperty(deviceName: Name, propertyName: String): Meta =
|
||||||
this[deviceName].readProperty(propertyName)
|
this[deviceName].readProperty(propertyName)
|
||||||
|
|
||||||
public suspend fun DeviceHub.writeItem(deviceName: Name, propertyName: String, value: Meta) {
|
public suspend fun DeviceHub.writeProperty(deviceName: Name, propertyName: String, value: Meta) {
|
||||||
this[deviceName].writeItem(propertyName, value)
|
this[deviceName].writeProperty(propertyName, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
public suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: Meta?): Meta? =
|
public suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: Meta?): Meta? =
|
||||||
|
@ -164,7 +164,7 @@ public abstract class DeviceBase(final override val context: Context) : Device {
|
|||||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate()
|
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeItem(propertyName: String, value: Meta) {
|
override suspend fun writeProperty(propertyName: String, value: Meta) {
|
||||||
(_properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write(
|
(_properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write(
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMess
|
|||||||
if (request.value == null) {
|
if (request.value == null) {
|
||||||
invalidate(request.property)
|
invalidate(request.property)
|
||||||
} else {
|
} else {
|
||||||
writeItem(request.property, request.value)
|
writeProperty(request.property, request.value)
|
||||||
}
|
}
|
||||||
PropertyChangedMessage(
|
PropertyChangedMessage(
|
||||||
property = request.property,
|
property = request.property,
|
||||||
|
@ -11,11 +11,7 @@ import ru.mipt.npm.controls.api.*
|
|||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.properties.Delegates.observable
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A device generated from specification
|
* A device generated from specification
|
||||||
@ -58,7 +54,7 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
|
|
||||||
private val stateLock = Mutex()
|
private val stateLock = Mutex()
|
||||||
|
|
||||||
private 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 {
|
||||||
logicalState[propertyName] = value
|
logicalState[propertyName] = value
|
||||||
@ -74,7 +70,7 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
* The logical state is updated after read
|
* The logical state is updated after read
|
||||||
*/
|
*/
|
||||||
override suspend fun readProperty(propertyName: String): Meta {
|
override suspend fun readProperty(propertyName: String): Meta {
|
||||||
val newValue = properties[propertyName]?.readItem(self)
|
val newValue = properties[propertyName]?.readMeta(self)
|
||||||
?: error("A property with name $propertyName is not registered in $this")
|
?: error("A property with name $propertyName is not registered in $this")
|
||||||
updateLogical(propertyName, newValue)
|
updateLogical(propertyName, newValue)
|
||||||
return newValue
|
return newValue
|
||||||
@ -88,10 +84,10 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeItem(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.writeItem(self, value)
|
it.writeMeta(self, value)
|
||||||
invalidate(propertyName)
|
invalidate(propertyName)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
updateLogical(propertyName, value)
|
updateLogical(propertyName, value)
|
||||||
@ -99,39 +95,23 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(action: String, argument: Meta?): Meta? =
|
override suspend fun execute(action: String, argument: Meta?): Meta? =
|
||||||
actions[action]?.executeItem(self, argument)
|
actions[action]?.executeMeta(self, argument)
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A delegate that represents the logical-only state of the device
|
|
||||||
*/
|
|
||||||
public fun <T : Any> state(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
initialValue: T,
|
|
||||||
): ReadWriteProperty<D, T> = observable(initialValue) { property: KProperty<*>, oldValue: T, newValue: T ->
|
|
||||||
if (oldValue != newValue) {
|
|
||||||
launch {
|
|
||||||
invalidate(property.name)
|
|
||||||
sharedMessageFlow.emit(PropertyChangedMessage(property.name, converter.objectToMeta(newValue)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read typed value and update/push event if needed
|
* Read typed value and update/push event if needed
|
||||||
*/
|
*/
|
||||||
public suspend fun <T : Any> DevicePropertySpec<D, T>.read(): T {
|
public suspend fun <T> DevicePropertySpec<D, T>.read(): T {
|
||||||
val res = read(self)
|
val res = read(self)
|
||||||
updateLogical(name, converter.objectToMeta(res))
|
updateLogical(name, converter.objectToMeta(res))
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> DevicePropertySpec<D, T>.get(): T? = getProperty(name)?.let(converter::metaToObject)
|
public fun <T> DevicePropertySpec<D, T>.get(): T? = getProperty(name)?.let(converter::metaToObject)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write typed property state and invalidate logical state
|
* Write typed property state and invalidate logical state
|
||||||
*/
|
*/
|
||||||
public suspend fun <T : Any> WritableDevicePropertySpec<D, T>.write(value: T) {
|
public suspend fun <T> WritableDevicePropertySpec<D, T>.write(value: T) {
|
||||||
write(self, value)
|
write(self, value)
|
||||||
invalidate(name)
|
invalidate(name)
|
||||||
}
|
}
|
||||||
@ -146,7 +126,7 @@ public suspend fun <D : DeviceBySpec<D>, T : Any> D.read(
|
|||||||
propertySpec: DevicePropertySpec<D, T>
|
propertySpec: DevicePropertySpec<D, T>
|
||||||
): T = propertySpec.read()
|
): T = propertySpec.read()
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>, T : Any> D.write(
|
public fun <D : DeviceBySpec<D>, T> D.write(
|
||||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||||
value: T
|
value: T
|
||||||
): Job = launch {
|
): Job = launch {
|
||||||
|
@ -5,8 +5,6 @@ import ru.mipt.npm.controls.api.Device
|
|||||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||||
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
|
||||||
import space.kscience.dataforge.meta.transformations.nullableMetaToObject
|
|
||||||
import space.kscience.dataforge.meta.transformations.nullableObjectToMeta
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,8 +13,7 @@ import space.kscience.dataforge.meta.transformations.nullableObjectToMeta
|
|||||||
@RequiresOptIn
|
@RequiresOptIn
|
||||||
public annotation class InternalDeviceAPI
|
public annotation class InternalDeviceAPI
|
||||||
|
|
||||||
//TODO relax T restriction after DF 0.4.4
|
public interface DevicePropertySpec<in D : Device, T> {
|
||||||
public interface DevicePropertySpec<in D : Device, T : Any> {
|
|
||||||
/**
|
/**
|
||||||
* Property name, should be unique in device
|
* Property name, should be unique in device
|
||||||
*/
|
*/
|
||||||
@ -40,11 +37,11 @@ public interface DevicePropertySpec<in D : Device, T : Any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public suspend fun <D : Device, T : Any> DevicePropertySpec<D, T>.readItem(device: D): Meta =
|
public suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta =
|
||||||
converter.objectToMeta(read(device))
|
converter.objectToMeta(read(device))
|
||||||
|
|
||||||
|
|
||||||
public interface WritableDevicePropertySpec<in D : Device, T : Any> : DevicePropertySpec<D, T> {
|
public interface WritableDevicePropertySpec<in D : Device, T> : DevicePropertySpec<D, T> {
|
||||||
/**
|
/**
|
||||||
* Write physical value to a device
|
* Write physical value to a device
|
||||||
*/
|
*/
|
||||||
@ -53,11 +50,11 @@ public interface WritableDevicePropertySpec<in D : Device, T : Any> : DeviceProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public suspend fun <D : Device, T : Any> WritableDevicePropertySpec<D, T>.writeItem(device: D, item: Meta) {
|
public suspend fun <D : Device, T> WritableDevicePropertySpec<D, T>.writeMeta(device: D, item: Meta) {
|
||||||
write(device, converter.metaToObject(item) ?: error("Meta $item could not be read with $converter"))
|
write(device, converter.metaToObject(item) ?: error("Meta $item could not be read with $converter"))
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DeviceActionSpec<in D : Device, I : Any, O : Any> {
|
public interface DeviceActionSpec<in D : Device, I, O> {
|
||||||
/**
|
/**
|
||||||
* Action name, should be unique in device
|
* Action name, should be unique in device
|
||||||
*/
|
*/
|
||||||
@ -78,11 +75,11 @@ public interface DeviceActionSpec<in D : Device, I : Any, O : Any> {
|
|||||||
public suspend fun execute(device: D, input: I?): O?
|
public suspend fun execute(device: D, input: I?): O?
|
||||||
}
|
}
|
||||||
|
|
||||||
public suspend fun <D : Device, I : Any, O : Any> DeviceActionSpec<D, I, O>.executeItem(
|
public suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeMeta(
|
||||||
device: D,
|
device: D,
|
||||||
item: Meta?
|
item: Meta?
|
||||||
): Meta? {
|
): Meta? {
|
||||||
val arg = inputConverter.nullableMetaToObject(item)
|
val arg = item?.let { inputConverter.metaToObject(item) }
|
||||||
val res = execute(device, arg)
|
val res = execute(device, arg)
|
||||||
return outputConverter.nullableObjectToMeta(res)
|
return res?.let { outputConverter.objectToMeta(res) }
|
||||||
}
|
}
|
@ -52,8 +52,9 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
override val name: String = readWriteProperty.name
|
override val name: String = readWriteProperty.name
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
override suspend fun read(device: D): T =
|
override suspend fun read(device: D): T = withContext(device.coroutineContext) {
|
||||||
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) = withContext(device.coroutineContext) {
|
||||||
readWriteProperty.set(device, value)
|
readWriteProperty.set(device, value)
|
||||||
@ -145,12 +146,12 @@ public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
|||||||
/**
|
/**
|
||||||
* The function is executed right after device initialization is finished
|
* The function is executed right after device initialization is finished
|
||||||
*/
|
*/
|
||||||
public open fun D.onStartup(){}
|
public open fun D.onStartup() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function is executed before device is shut down
|
* The function is executed before device is shut down
|
||||||
*/
|
*/
|
||||||
public open fun D.onShutdown(){}
|
public open fun D.onShutdown() {}
|
||||||
|
|
||||||
|
|
||||||
override fun invoke(meta: Meta, context: Context): D = buildDevice().apply {
|
override fun invoke(meta: Meta, context: Context): D = buildDevice().apply {
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package ru.mipt.npm.controls.properties
|
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> D.state(
|
|
||||||
initialValue: Double,
|
|
||||||
): ReadWriteProperty<D, Double> = state(MetaConverter.double, initialValue)
|
|
||||||
|
|
||||||
public fun <D : DeviceBySpec<D>> D.state(
|
|
||||||
initialValue: Number,
|
|
||||||
): ReadWriteProperty<D, Number> = state(MetaConverter.number, initialValue)
|
|
@ -94,7 +94,7 @@ public fun DeviceManager.launchTangoMagix(
|
|||||||
}
|
}
|
||||||
TangoAction.write -> {
|
TangoAction.write -> {
|
||||||
request.payload.value?.let { value ->
|
request.payload.value?.let { value ->
|
||||||
device.writeItem(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.getOrReadItem(request.payload.name)
|
||||||
|
@ -8,6 +8,7 @@ val miloVersion: String = "0.6.3"
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":controls-core"))
|
api(project(":controls-core"))
|
||||||
|
api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}")
|
||||||
implementation("org.eclipse.milo:sdk-client:$miloVersion")
|
implementation("org.eclipse.milo:sdk-client:$miloVersion")
|
||||||
implementation("org.eclipse.milo:bsd-parser:$miloVersion")
|
implementation("org.eclipse.milo:bsd-parser:$miloVersion")
|
||||||
implementation("org.eclipse.milo:dictionary-reader:$miloVersion")
|
implementation("org.eclipse.milo:dictionary-reader:$miloVersion")
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
package ru.mipt.npm.controls.opcua
|
package ru.mipt.npm.controls.opcua
|
||||||
|
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||||
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue
|
import org.eclipse.milo.opcua.stack.core.types.builtin.*
|
||||||
import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject
|
|
||||||
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
|
|
||||||
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant
|
|
||||||
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn
|
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn
|
||||||
import ru.mipt.npm.controls.api.Device
|
import ru.mipt.npm.controls.api.Device
|
||||||
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
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,40 +24,46 @@ public interface MiloDevice : Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline fun <reified T> MiloDevice.opc(
|
public suspend inline fun <reified T> MiloDevice.readOpcWithTime(
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
magAge: Double = 500.0
|
magAge: Double = 500.0
|
||||||
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
|
): Pair<T, DateTime> {
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
val data = client.readValue(magAge, TimestampsToReturn.Server, nodeId).await()
|
||||||
val data = client.readValue(magAge, TimestampsToReturn.Server, nodeId).get()
|
val time = data.serverTime ?: error("No server time provided")
|
||||||
val meta: Meta = when (val content = data.value.value) {
|
val meta: Meta = when (val content = data.value.value) {
|
||||||
is T -> return content
|
is T -> return content to time
|
||||||
content is Meta -> content as Meta
|
content is Meta -> content as Meta
|
||||||
content is ExtensionObject -> (content as ExtensionObject).decode(client.dynamicSerializationContext) as Meta
|
content is ExtensionObject -> (content as ExtensionObject).decode(client.dynamicSerializationContext) as Meta
|
||||||
else -> error("Incompatible OPC property value $content")
|
else -> error("Incompatible OPC property value $content")
|
||||||
}
|
|
||||||
|
|
||||||
return converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
val res = converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
|
||||||
val meta = converter.objectToMeta(value)
|
return res to time
|
||||||
client.writeValue(nodeId, DataValue(Variant(meta)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline fun <reified T> MiloDevice.opcDouble(
|
public suspend inline fun <reified T> MiloDevice.readOpc(
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
magAge: Double = 1.0
|
converter: MetaConverter<T>,
|
||||||
): ReadWriteProperty<Any?, Double> = opc(nodeId, MetaConverter.double, magAge)
|
magAge: Double = 500.0
|
||||||
|
): T {
|
||||||
|
val data = client.readValue(magAge, TimestampsToReturn.Neither, nodeId).await()
|
||||||
|
|
||||||
public inline fun <reified T> MiloDevice.opcInt(
|
val meta: Meta = when (val content = data.value.value) {
|
||||||
nodeId: NodeId,
|
is T -> return content
|
||||||
magAge: Double = 1.0
|
content is Meta -> content as Meta
|
||||||
): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge)
|
content is ExtensionObject -> (content as ExtensionObject).decode(client.dynamicSerializationContext) as Meta
|
||||||
|
else -> error("Incompatible OPC property value $content")
|
||||||
|
}
|
||||||
|
|
||||||
public inline fun <reified T> MiloDevice.opcString(
|
return converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
public suspend inline fun <reified T> MiloDevice.writeOpc(
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
magAge: Double = 1.0
|
converter: MetaConverter<T>,
|
||||||
): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)
|
value: T
|
||||||
|
): StatusCode {
|
||||||
|
val meta = converter.objectToMeta(value)
|
||||||
|
return client.writeValue(nodeId, DataValue(Variant(meta))).await()
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
package ru.mipt.npm.controls.opcua
|
package ru.mipt.npm.controls.opcua
|
||||||
|
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
|
||||||
import ru.mipt.npm.controls.properties.DeviceBySpec
|
import ru.mipt.npm.controls.properties.DeviceBySpec
|
||||||
import ru.mipt.npm.controls.properties.DeviceSpec
|
import ru.mipt.npm.controls.properties.DeviceSpec
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
@ -8,12 +11,15 @@ import space.kscience.dataforge.context.Global
|
|||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
import space.kscience.dataforge.meta.string
|
import space.kscience.dataforge.meta.string
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
public open class MiloDeviceBySpec<D: MiloDeviceBySpec<D>>(
|
public open class MiloDeviceBySpec<D : MiloDeviceBySpec<D>>(
|
||||||
spec: DeviceSpec<D>,
|
spec: DeviceSpec<D>,
|
||||||
context: Context = Global,
|
context: Context = Global,
|
||||||
meta: Meta = Meta.EMPTY
|
meta: Meta = Meta.EMPTY
|
||||||
): MiloDevice, DeviceBySpec<D>(spec, context, meta) {
|
) : MiloDevice, DeviceBySpec<D>(spec, context, meta) {
|
||||||
|
|
||||||
override val client: OpcUaClient by lazy {
|
override val client: OpcUaClient by lazy {
|
||||||
val endpointUrl = meta["endpointUrl"].string ?: error("Endpoint url is not defined")
|
val endpointUrl = meta["endpointUrl"].string ?: error("Endpoint url is not defined")
|
||||||
@ -26,4 +32,38 @@ public open class MiloDeviceBySpec<D: MiloDeviceBySpec<D>>(
|
|||||||
super<MiloDevice>.close()
|
super<MiloDevice>.close()
|
||||||
super<DeviceBySpec>.close()
|
super<DeviceBySpec>.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A device-bound OPC-UA property. Does not trigger device properties change.
|
||||||
|
*/
|
||||||
|
public inline fun <reified T> MiloDeviceBySpec<*>.opc(
|
||||||
|
nodeId: NodeId,
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
magAge: Double = 500.0
|
||||||
|
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T = runBlocking {
|
||||||
|
readOpc(nodeId, converter, magAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
|
launch {
|
||||||
|
writeOpc(nodeId, converter, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun <reified T> MiloDeviceBySpec<*>.opcDouble(
|
||||||
|
nodeId: NodeId,
|
||||||
|
magAge: Double = 1.0
|
||||||
|
): ReadWriteProperty<Any?, Double> = opc(nodeId, MetaConverter.double, magAge)
|
||||||
|
|
||||||
|
public inline fun <reified T> MiloDeviceBySpec<*>.opcInt(
|
||||||
|
nodeId: NodeId,
|
||||||
|
magAge: Double = 1.0
|
||||||
|
): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge)
|
||||||
|
|
||||||
|
public inline fun <reified T> MiloDeviceBySpec<*>.opcString(
|
||||||
|
nodeId: NodeId,
|
||||||
|
magAge: Double = 1.0
|
||||||
|
): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)
|
@ -10,6 +10,9 @@ import ru.mipt.npm.controls.api.DeviceMessage
|
|||||||
import ru.mipt.npm.controls.client.connectToMagix
|
import ru.mipt.npm.controls.client.connectToMagix
|
||||||
import ru.mipt.npm.controls.controllers.DeviceManager
|
import ru.mipt.npm.controls.controllers.DeviceManager
|
||||||
import ru.mipt.npm.controls.controllers.install
|
import ru.mipt.npm.controls.controllers.install
|
||||||
|
import ru.mipt.npm.controls.demo.DemoDevice.Companion.cosScale
|
||||||
|
import ru.mipt.npm.controls.demo.DemoDevice.Companion.sinScale
|
||||||
|
import ru.mipt.npm.controls.demo.DemoDevice.Companion.timeScale
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||||
import ru.mipt.npm.magix.rsocket.rSocketWithTcp
|
import ru.mipt.npm.magix.rsocket.rSocketWithTcp
|
||||||
import ru.mipt.npm.magix.rsocket.rSocketWithWebSockets
|
import ru.mipt.npm.magix.rsocket.rSocketWithWebSockets
|
||||||
@ -97,10 +100,12 @@ class DemoControllerView : View(title = " Demo controller remote") {
|
|||||||
button("Submit") {
|
button("Submit") {
|
||||||
useMaxWidth = true
|
useMaxWidth = true
|
||||||
action {
|
action {
|
||||||
controller.device?.apply {
|
controller.device?.run {
|
||||||
timeScale = timeScaleSlider.value
|
launch {
|
||||||
sinScale = xScaleSlider.value
|
timeScale.write(timeScaleSlider.value)
|
||||||
cosScale = yScaleSlider.value
|
sinScale.write(xScaleSlider.value)
|
||||||
|
cosScale.write(yScaleSlider.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,24 +9,24 @@ import kotlin.time.ExperimentalTime
|
|||||||
|
|
||||||
|
|
||||||
class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
||||||
var timeScale by state(5000.0)
|
private var timeScaleState = 5000.0
|
||||||
var sinScale by state(1.0)
|
private var sinScaleState = 1.0
|
||||||
var cosScale by state(1.0)
|
private var cosScaleState = 1.0
|
||||||
|
|
||||||
companion object : DeviceSpec<DemoDevice>(::DemoDevice) {
|
companion object : DeviceSpec<DemoDevice>(::DemoDevice) {
|
||||||
// register virtual properties based on actual object state
|
// register virtual properties based on actual object state
|
||||||
val timeScaleProperty = registerProperty(MetaConverter.double, DemoDevice::timeScale)
|
val timeScale = registerProperty(MetaConverter.double, DemoDevice::timeScaleState)
|
||||||
val sinScaleProperty = registerProperty(MetaConverter.double, DemoDevice::sinScale)
|
val sinScale = registerProperty(MetaConverter.double, DemoDevice::sinScaleState)
|
||||||
val cosScaleProperty = registerProperty(MetaConverter.double, DemoDevice::cosScale)
|
val cosScale = registerProperty(MetaConverter.double, DemoDevice::cosScaleState)
|
||||||
|
|
||||||
val sin by doubleProperty {
|
val sin by doubleProperty {
|
||||||
val time = Instant.now()
|
val time = Instant.now()
|
||||||
kotlin.math.sin(time.toEpochMilli().toDouble() / timeScale) * sinScale
|
kotlin.math.sin(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
|
||||||
}
|
}
|
||||||
|
|
||||||
val cos by doubleProperty {
|
val cos by doubleProperty {
|
||||||
val time = Instant.now()
|
val time = Instant.now()
|
||||||
kotlin.math.cos(time.toEpochMilli().toDouble() / timeScale) * sinScale
|
kotlin.math.cos(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
|
||||||
}
|
}
|
||||||
|
|
||||||
val coordinates by metaProperty {
|
val coordinates by metaProperty {
|
||||||
@ -39,9 +39,9 @@ class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
|
val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
|
||||||
timeScale = 5000.0
|
timeScale.write(5000.0)
|
||||||
sinScale = 1.0
|
sinScale.write(1.0)
|
||||||
cosScale = 1.0
|
cosScale.write(1.0)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user