Properties refactoring
This commit is contained in:
parent
0e6363048e
commit
7413bda5a1
@ -45,6 +45,10 @@ abstract class DeviceBase : Device {
|
||||
return properties.getOrPut(name, builder)
|
||||
}
|
||||
|
||||
internal fun registerMutableProperty(name: String, builder: () -> DeviceProperty): DeviceProperty {
|
||||
return properties.getOrPut(name, builder) as DeviceProperty
|
||||
}
|
||||
|
||||
internal fun registerAction(name: String, builder: () -> Action): Action {
|
||||
return actions.getOrPut(name, builder)
|
||||
}
|
||||
|
@ -9,52 +9,52 @@ import kotlin.time.Duration
|
||||
/**
|
||||
* Read-only device property
|
||||
*/
|
||||
interface ReadOnlyDeviceProperty {
|
||||
public interface ReadOnlyDeviceProperty {
|
||||
/**
|
||||
* Property name, should be unique in device
|
||||
*/
|
||||
val name: String
|
||||
public val name: String
|
||||
|
||||
/**
|
||||
* Property descriptor
|
||||
*/
|
||||
val descriptor: PropertyDescriptor
|
||||
public val descriptor: PropertyDescriptor
|
||||
|
||||
val scope: CoroutineScope
|
||||
public val scope: CoroutineScope
|
||||
|
||||
/**
|
||||
* Erase logical value and force re-read from device on next [read]
|
||||
*/
|
||||
suspend fun invalidate()
|
||||
public suspend fun invalidate()
|
||||
|
||||
/**
|
||||
* Directly update property logical value and notify listener without writing it to device
|
||||
*/
|
||||
public fun updateLogical(item: MetaItem<*>)
|
||||
|
||||
// /**
|
||||
// * Update property logical value and notify listener without writing it to device
|
||||
// */
|
||||
// suspend fun update(item: MetaItem<*>)
|
||||
//
|
||||
/**
|
||||
* Get cached value and return null if value is invalid or not initialized
|
||||
*/
|
||||
val value: MetaItem<*>?
|
||||
public val value: MetaItem<*>?
|
||||
|
||||
/**
|
||||
* Read value either from cache if cache is valid or directly from physical device.
|
||||
* If [force], reread
|
||||
* If [force], reread from physical state even if the logical state is set.
|
||||
*/
|
||||
suspend fun read(force: Boolean = false): MetaItem<*>
|
||||
public suspend fun read(force: Boolean = false): MetaItem<*>
|
||||
|
||||
/**
|
||||
* The [Flow] representing future logical states of the property.
|
||||
* Produces null when the state is invalidated
|
||||
*/
|
||||
fun flow(): Flow<MetaItem<*>?>
|
||||
public fun flow(): Flow<MetaItem<*>?>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Launch recurring force re-read job on a property scope with given [duration] between reads.
|
||||
*/
|
||||
fun ReadOnlyDeviceProperty.readEvery(duration: Duration): Job = scope.launch {
|
||||
public fun ReadOnlyDeviceProperty.readEvery(duration: Duration): Job = scope.launch {
|
||||
while (isActive) {
|
||||
read(true)
|
||||
delay(duration)
|
||||
@ -64,11 +64,11 @@ fun ReadOnlyDeviceProperty.readEvery(duration: Duration): Job = scope.launch {
|
||||
/**
|
||||
* A writeable device property with non-suspended write
|
||||
*/
|
||||
interface DeviceProperty : ReadOnlyDeviceProperty {
|
||||
public interface DeviceProperty : ReadOnlyDeviceProperty {
|
||||
override var value: MetaItem<*>?
|
||||
|
||||
/**
|
||||
* Write value to physical device. Invalidates logical value, but does not update it automatically
|
||||
*/
|
||||
suspend fun write(item: MetaItem<*>)
|
||||
public suspend fun write(item: MetaItem<*>)
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
package hep.dataforge.control.base
|
||||
|
||||
import hep.dataforge.control.api.PropertyDescriptor
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.double
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.asValue
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -26,7 +24,7 @@ private fun DeviceBase.propertyChanged(name: String, item: MetaItem<*>?){
|
||||
* A stand-alone [ReadOnlyDeviceProperty] implementation not directly attached to a device
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
open class IsolatedReadOnlyDeviceProperty(
|
||||
public open class IsolatedReadOnlyDeviceProperty(
|
||||
override val name: String,
|
||||
default: MetaItem<*>?,
|
||||
override val descriptor: PropertyDescriptor,
|
||||
@ -42,7 +40,7 @@ open class IsolatedReadOnlyDeviceProperty(
|
||||
state.value = null
|
||||
}
|
||||
|
||||
protected fun update(item: MetaItem<*>) {
|
||||
override fun updateLogical(item: MetaItem<*>) {
|
||||
state.value = item
|
||||
callback(name, item)
|
||||
}
|
||||
@ -56,7 +54,7 @@ open class IsolatedReadOnlyDeviceProperty(
|
||||
//TODO add error catching
|
||||
getter(currentValue)
|
||||
}
|
||||
update(res)
|
||||
updateLogical(res)
|
||||
res
|
||||
} else {
|
||||
currentValue
|
||||
@ -66,7 +64,7 @@ open class IsolatedReadOnlyDeviceProperty(
|
||||
override fun flow(): StateFlow<MetaItem<*>?> = state
|
||||
}
|
||||
|
||||
fun DeviceBase.readOnlyProperty(
|
||||
public fun DeviceBase.readOnlyProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
@ -87,9 +85,9 @@ private class ReadOnlyDevicePropertyDelegate<D : DeviceBase>(
|
||||
val default: MetaItem<*>?,
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
||||
) : ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> {
|
||||
) : ReadOnlyProperty<D, ReadOnlyDeviceProperty> {
|
||||
|
||||
override fun getValue(thisRef: D, property: KProperty<*>): IsolatedReadOnlyDeviceProperty {
|
||||
override fun getValue(thisRef: D, property: KProperty<*>): ReadOnlyDeviceProperty {
|
||||
val name = property.name
|
||||
|
||||
return owner.registerProperty(name) {
|
||||
@ -102,37 +100,37 @@ private class ReadOnlyDevicePropertyDelegate<D : DeviceBase>(
|
||||
owner::propertyChanged,
|
||||
getter
|
||||
)
|
||||
} as IsolatedReadOnlyDeviceProperty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <D : DeviceBase> D.reading(
|
||||
public fun <D : DeviceBase> D.reading(
|
||||
default: MetaItem<*>? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
||||
): ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default,
|
||||
descriptorBuilder,
|
||||
getter
|
||||
)
|
||||
|
||||
fun <D : DeviceBase> D.readingValue(
|
||||
public fun <D : DeviceBase> D.readingValue(
|
||||
default: Value? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> Any
|
||||
): ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
getter: suspend () -> Any?
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it) },
|
||||
descriptorBuilder,
|
||||
getter = { MetaItem.ValueItem(Value.of(getter())) }
|
||||
)
|
||||
|
||||
fun <D : DeviceBase> D.readingNumber(
|
||||
public fun <D : DeviceBase> D.readingNumber(
|
||||
default: Number? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> Number
|
||||
): ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
descriptorBuilder,
|
||||
@ -142,11 +140,25 @@ fun <D : DeviceBase> D.readingNumber(
|
||||
}
|
||||
)
|
||||
|
||||
fun <D : DeviceBase> D.readingMeta(
|
||||
public fun <D : DeviceBase> D.readingString(
|
||||
default: Number? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend () -> String
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
||||
descriptorBuilder,
|
||||
getter = {
|
||||
val number = getter()
|
||||
MetaItem.ValueItem(number.asValue())
|
||||
}
|
||||
)
|
||||
|
||||
public fun <D : DeviceBase> D.readingMeta(
|
||||
default: Meta? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend MetaBuilder.() -> Unit
|
||||
): ReadOnlyProperty<D, IsolatedReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
||||
this,
|
||||
default?.let { MetaItem.NodeItem(it) },
|
||||
descriptorBuilder,
|
||||
@ -156,7 +168,7 @@ fun <D : DeviceBase> D.readingMeta(
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class IsolatedDeviceProperty(
|
||||
public class IsolatedDeviceProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptor: PropertyDescriptor,
|
||||
@ -189,20 +201,20 @@ class IsolatedDeviceProperty(
|
||||
withContext(scope.coroutineContext) {
|
||||
//TODO add error catching
|
||||
setter(oldValue, item)?.let {
|
||||
update(it)
|
||||
updateLogical(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun DeviceBase.mutableProperty(
|
||||
public fun DeviceBase.mutableProperty(
|
||||
name: String,
|
||||
default: MetaItem<*>?,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||
): ReadOnlyDeviceProperty = registerProperty(name) {
|
||||
): DeviceProperty = registerMutableProperty(name) {
|
||||
IsolatedDeviceProperty(
|
||||
name,
|
||||
default,
|
||||
@ -220,11 +232,11 @@ private class DevicePropertyDelegate<D : DeviceBase>(
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||
) : ReadOnlyProperty<D, IsolatedDeviceProperty> {
|
||||
) : ReadOnlyProperty<D, DeviceProperty> {
|
||||
|
||||
override fun getValue(thisRef: D, property: KProperty<*>): IsolatedDeviceProperty {
|
||||
val name = property.name
|
||||
return owner.registerProperty(name) {
|
||||
return owner.registerMutableProperty(name) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
IsolatedDeviceProperty(
|
||||
name,
|
||||
@ -239,12 +251,12 @@ private class DevicePropertyDelegate<D : DeviceBase>(
|
||||
}
|
||||
}
|
||||
|
||||
fun <D : DeviceBase> D.writing(
|
||||
public fun <D : DeviceBase> D.writing(
|
||||
default: MetaItem<*>? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
||||
): ReadOnlyProperty<D, IsolatedDeviceProperty> = DevicePropertyDelegate(
|
||||
): ReadOnlyProperty<D, DeviceProperty> = DevicePropertyDelegate(
|
||||
this,
|
||||
default,
|
||||
descriptorBuilder,
|
||||
@ -252,31 +264,31 @@ fun <D : DeviceBase> D.writing(
|
||||
setter
|
||||
)
|
||||
|
||||
fun <D : DeviceBase> D.writingVirtual(
|
||||
public fun <D : DeviceBase> D.writingVirtual(
|
||||
default: MetaItem<*>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): ReadOnlyProperty<D, IsolatedDeviceProperty> = writing(
|
||||
): ReadOnlyProperty<D, DeviceProperty> = writing(
|
||||
default,
|
||||
descriptorBuilder,
|
||||
getter = { it ?: default },
|
||||
setter = { _, newItem -> newItem }
|
||||
)
|
||||
|
||||
fun <D : DeviceBase> D.writingVirtual(
|
||||
public fun <D : DeviceBase> D.writingVirtual(
|
||||
default: Value,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): ReadOnlyProperty<D, IsolatedDeviceProperty> = writing(
|
||||
): ReadOnlyProperty<D, DeviceProperty> = writing(
|
||||
MetaItem.ValueItem(default),
|
||||
descriptorBuilder,
|
||||
getter = { it ?: MetaItem.ValueItem(default) },
|
||||
setter = { _, newItem -> newItem }
|
||||
)
|
||||
|
||||
fun <D : DeviceBase> D.writingDouble(
|
||||
public fun <D : DeviceBase> D.writingDouble(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (Double) -> Double,
|
||||
setter: suspend (oldValue: Double?, newValue: Double) -> Double?
|
||||
): ReadOnlyProperty<D, IsolatedDeviceProperty> {
|
||||
): ReadOnlyProperty<D, DeviceProperty> {
|
||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||
MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue())
|
||||
}
|
||||
@ -292,4 +304,26 @@ fun <D : DeviceBase> D.writingDouble(
|
||||
innerGetter,
|
||||
innerSetter
|
||||
)
|
||||
}
|
||||
|
||||
public fun <D : DeviceBase> D.writingBoolean(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
getter: suspend (Boolean?) -> Boolean,
|
||||
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?
|
||||
): ReadOnlyProperty<D, DeviceProperty> {
|
||||
val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = {
|
||||
MetaItem.ValueItem(getter(it.boolean).asValue())
|
||||
}
|
||||
|
||||
val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue ->
|
||||
setter(oldValue.boolean, newValue.boolean?: error("Can't convert $newValue to boolean"))?.asValue()?.asMetaItem()
|
||||
}
|
||||
|
||||
return DevicePropertyDelegate(
|
||||
this,
|
||||
MetaItem.ValueItem(Null),
|
||||
descriptorBuilder,
|
||||
innerGetter,
|
||||
innerSetter
|
||||
)
|
||||
}
|
@ -5,6 +5,7 @@ import hep.dataforge.control.api.DeviceHub
|
||||
import hep.dataforge.control.api.DeviceListener
|
||||
import hep.dataforge.control.api.get
|
||||
import hep.dataforge.control.controllers.DeviceMessage.Companion.PROPERTY_CHANGED_ACTION
|
||||
import hep.dataforge.io.Consumer
|
||||
import hep.dataforge.io.Envelope
|
||||
import hep.dataforge.io.Responder
|
||||
import hep.dataforge.io.SimpleEnvelope
|
||||
@ -168,7 +169,7 @@ class DeviceController(
|
||||
suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage {
|
||||
return try {
|
||||
val targetName = request.target?.toName() ?: Name.EMPTY
|
||||
val device = this[targetName]
|
||||
val device = this[targetName] ?: error("The device with name $targetName not found in $this")
|
||||
DeviceController.respondMessage(device, targetName.toString(), request)
|
||||
} catch (ex: Exception) {
|
||||
DeviceMessage.fail {
|
||||
|
@ -8,6 +8,7 @@ import hep.dataforge.control.api.Device
|
||||
import hep.dataforge.control.api.DeviceHub
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class DeviceManager : AbstractPlugin(), DeviceHub {
|
||||
@ -16,14 +17,14 @@ class DeviceManager : AbstractPlugin(), DeviceHub {
|
||||
/**
|
||||
* Actual list of connected devices
|
||||
*/
|
||||
private val top = HashMap<Name, Device>()
|
||||
override val devices: Map<Name, Device> get() = top
|
||||
private val top = HashMap<NameToken, Device>()
|
||||
override val devices: Map<NameToken, Device> get() = top
|
||||
|
||||
val controller by lazy {
|
||||
HubController(this, context)
|
||||
}
|
||||
|
||||
fun registerDevice(name: Name, device: Device) {
|
||||
fun registerDevice(name: NameToken, device: Device) {
|
||||
top[name] = device
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
package hep.dataforge.control.controllers
|
||||
|
||||
import hep.dataforge.control.api.Consumer
|
||||
import hep.dataforge.control.api.DeviceHub
|
||||
import hep.dataforge.control.api.DeviceListener
|
||||
import hep.dataforge.control.api.get
|
||||
import hep.dataforge.io.Consumer
|
||||
import hep.dataforge.io.Envelope
|
||||
import hep.dataforge.io.Responder
|
||||
import hep.dataforge.meta.MetaItem
|
||||
@ -11,6 +11,7 @@ import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.meta.wrap
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.toName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
@ -38,7 +39,7 @@ class HubController(
|
||||
}
|
||||
}
|
||||
|
||||
private val listeners: Map<Name, DeviceListener> = hub.devices.mapValues { (name, device) ->
|
||||
private val listeners: Map<NameToken, DeviceListener> = hub.devices.mapValues { (name, device) ->
|
||||
object : DeviceListener {
|
||||
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
|
||||
if (value == null) return
|
||||
@ -62,7 +63,7 @@ class HubController(
|
||||
|
||||
suspend fun respondMessage(message: DeviceMessage): DeviceMessage = try {
|
||||
val targetName = message.target?.toName() ?: Name.EMPTY
|
||||
val device = hub[targetName]
|
||||
val device = hub[targetName] ?: error("The device with name $targetName not found in $hub")
|
||||
DeviceController.respondMessage(device, targetName.toString(), message)
|
||||
} catch (ex: Exception) {
|
||||
DeviceMessage.fail {
|
||||
@ -72,7 +73,7 @@ class HubController(
|
||||
|
||||
override suspend fun respond(request: Envelope): Envelope = try {
|
||||
val targetName = request.meta[DeviceMessage.TARGET_KEY].string?.toName() ?: Name.EMPTY
|
||||
val device = hub[targetName]
|
||||
val device = hub[targetName] ?: error("The device with name $targetName not found in $hub")
|
||||
if (request.data == null) {
|
||||
DeviceController.respondMessage(device, targetName.toString(), DeviceMessage.wrap(request.meta)).wrap()
|
||||
} else {
|
||||
|
@ -115,7 +115,7 @@ public fun Application.deviceModule(
|
||||
+"Device server dashboard"
|
||||
}
|
||||
deviceNames.forEach { deviceName ->
|
||||
val device = manager[deviceName]
|
||||
val device = manager[deviceName] ?: error("The device with name $deviceName not found in $manager")
|
||||
div {
|
||||
id = deviceName
|
||||
h2 { +deviceName }
|
||||
|
@ -25,7 +25,7 @@ class DemoDevice(parentScope: CoroutineScope) : DeviceBase() {
|
||||
parentScope.coroutineContext + executor.asCoroutineDispatcher() + Job(parentScope.coroutineContext[Job])
|
||||
)
|
||||
|
||||
val timeScale: IsolatedDeviceProperty by writingVirtual(5000.0.asValue())
|
||||
val timeScale: DeviceProperty by writingVirtual(5000.0.asValue())
|
||||
var timeScaleValue by timeScale.double()
|
||||
|
||||
val sinScale by writingVirtual(1.0.asValue())
|
||||
|
@ -5,7 +5,7 @@ import hep.dataforge.control.controllers.devices
|
||||
import hep.dataforge.control.server.startDeviceServer
|
||||
import hep.dataforge.control.server.whenStarted
|
||||
import hep.dataforge.meta.double
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.NameToken
|
||||
import io.ktor.server.engine.ApplicationEngine
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
@ -50,7 +50,7 @@ suspend fun Trace.updateXYFrom(flow: Flow<Iterable<Pair<Double, Double>>>) {
|
||||
|
||||
|
||||
fun startDemoDeviceServer(context: Context, device: DemoDevice): ApplicationEngine {
|
||||
context.devices.registerDevice("demo".asName(), device)
|
||||
context.devices.registerDevice(NameToken("demo"), device)
|
||||
val server = context.startDeviceServer(context.devices)
|
||||
server.whenStarted {
|
||||
plotlyModule("plots").apply {
|
||||
|
@ -5,7 +5,6 @@ plugins {
|
||||
|
||||
//TODO to be moved to a separate project
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(project(":dataforge-device-core"))
|
||||
}
|
||||
|
@ -1,18 +1,25 @@
|
||||
package ru.mipt.npm.devices.pimotionmaster
|
||||
|
||||
import hep.dataforge.control.base.DeviceBase
|
||||
import hep.dataforge.control.base.DeviceProperty
|
||||
import hep.dataforge.control.base.writingVirtual
|
||||
import hep.dataforge.control.base.*
|
||||
import hep.dataforge.control.ports.Port
|
||||
import hep.dataforge.control.ports.PortProxy
|
||||
import hep.dataforge.control.ports.send
|
||||
import hep.dataforge.control.ports.withDelimiter
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.values.Null
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
public class PiMotionMasterDevice(
|
||||
parentScope: CoroutineScope,
|
||||
private val portFactory: suspend (MetaItem<*>?) -> Port,
|
||||
) : DeviceBase() {
|
||||
|
||||
class PiMotionMasterDevice(parentScope: CoroutineScope, val portFactory: suspend (MetaItem<*>?) -> Port) : DeviceBase() {
|
||||
override val scope: CoroutineScope = CoroutineScope(
|
||||
parentScope.coroutineContext + Job(parentScope.coroutineContext[Job])
|
||||
)
|
||||
@ -23,14 +30,81 @@ class PiMotionMasterDevice(parentScope: CoroutineScope, val portFactory: suspend
|
||||
|
||||
private val connector = PortProxy { portFactory(port.value) }
|
||||
|
||||
private suspend fun readPhrase(command: String) {
|
||||
connector.receiving().withDelimiter("\n").first { it.startsWith(command) }
|
||||
private val mutex = Mutex()
|
||||
|
||||
private suspend fun sendCommand(command: String, vararg arguments: String) {
|
||||
val joinedArguments = if (arguments.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
arguments.joinToString(prefix = " ", separator = " ", postfix = "")
|
||||
}
|
||||
val stringToSend = "$command$joinedArguments\n"
|
||||
connector.send(stringToSend)
|
||||
}
|
||||
|
||||
//
|
||||
// val firmwareVersion by reading {
|
||||
// connector.r
|
||||
// }
|
||||
/**
|
||||
* Send a synchronous request and receive a list of lines as a response
|
||||
*/
|
||||
private suspend fun request(command: String, vararg arguments: String): List<String> = mutex.withLock {
|
||||
sendCommand(command, *arguments)
|
||||
val phrases = connector.receiving().withDelimiter("\n")
|
||||
return@withLock phrases.takeWhile { it.endsWith(" \n") }.toList() + phrases.first()
|
||||
}
|
||||
|
||||
private suspend fun requestAndParse(command: String, vararg arguments: String): Map<String, String> = buildMap {
|
||||
request(command, *arguments).forEach { line ->
|
||||
val (key, value) = line.split("=")
|
||||
put(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a synchronous command
|
||||
*/
|
||||
private suspend fun send(command: String, vararg arguments: String) {
|
||||
mutex.withLock {
|
||||
sendCommand(command, *arguments)
|
||||
}
|
||||
}
|
||||
|
||||
public val initialize: Action by action {
|
||||
send("INI")
|
||||
}
|
||||
|
||||
public val firmwareVersion: ReadOnlyDeviceProperty by readingString {
|
||||
request("VER?").first()
|
||||
}
|
||||
|
||||
public inner class Axis(public val axisId: String) : DeviceBase() {
|
||||
override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope
|
||||
public val enabled: DeviceProperty by writingBoolean<Axis>(
|
||||
getter = {
|
||||
val result = requestAndParse("EAX?", axisId)[axisId]?.toIntOrNull()
|
||||
?: error("Malformed response. Should include integer value for $axisId")
|
||||
result != 0
|
||||
},
|
||||
setter = { oldValue, newValue ->
|
||||
val value = if(newValue){
|
||||
"1"
|
||||
} else {
|
||||
"0"
|
||||
}
|
||||
send("EAX", axisId, value)
|
||||
oldValue
|
||||
}
|
||||
)
|
||||
|
||||
public val halt: Action by action {
|
||||
send("HLT", axisId)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
//list everything here to ensure it is initialized
|
||||
initialize
|
||||
firmwareVersion
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user