Add port demo. Nullable properties
This commit is contained in:
parent
5b16b07172
commit
ba84553be5
@ -57,7 +57,7 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
||||
|
||||
/**
|
||||
* 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 writeProperty(propertyName: String, value: Meta)
|
||||
|
||||
@ -101,7 +101,7 @@ public suspend fun Device.getOrReadProperty(propertyName: String): Meta =
|
||||
*
|
||||
* TODO currently this
|
||||
*/
|
||||
public fun Device.getProperties(): Meta = Meta {
|
||||
public fun Device.getAllProperties(): Meta = Meta {
|
||||
for (descriptor in propertyDescriptors) {
|
||||
setMeta(Name.parse(descriptor.name), getProperty(descriptor.name))
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A generic bi-directional sender/receiver object
|
||||
* A generic bidirectional sender/receiver object
|
||||
*/
|
||||
public interface Socket<T> : Closeable {
|
||||
/**
|
||||
|
@ -6,11 +6,19 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import space.kscience.controls.api.Socket
|
||||
import space.kscience.dataforge.context.*
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
public interface Port : ContextAware, Socket<ByteArray>
|
||||
|
||||
public typealias PortFactory = Factory<Port>
|
||||
@Type(PortFactory.TYPE)
|
||||
public interface PortFactory: Factory<Port>{
|
||||
public val type: String
|
||||
|
||||
public companion object{
|
||||
public const val TYPE: String = "controls.port"
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AbstractPort(
|
||||
override val context: Context,
|
||||
@ -64,12 +72,10 @@ public abstract class AbstractPort(
|
||||
|
||||
/**
|
||||
* Raw flow of incoming data chunks. The chunks are not guaranteed to be complete phrases.
|
||||
* In order to form phrases some condition should used on top of it.
|
||||
* In order to form phrases some condition should be used on top of it.
|
||||
* For example [delimitedIncoming] generates phrases with fixed delimiter.
|
||||
*/
|
||||
override fun receiving(): Flow<ByteArray> {
|
||||
return incoming.receiveAsFlow()
|
||||
}
|
||||
override fun receiving(): Flow<ByteArray> = incoming.receiveAsFlow()
|
||||
|
||||
override fun close() {
|
||||
outgoing.close()
|
||||
|
@ -0,0 +1,34 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import space.kscience.dataforge.context.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.string
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class Ports : AbstractPlugin() {
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
private val portFactories by lazy {
|
||||
context.gather<PortFactory>(PortFactory.TYPE)
|
||||
}
|
||||
|
||||
private val portCache = mutableMapOf<Meta, Port>()
|
||||
|
||||
public fun buildPort(meta: Meta): Port = portCache.getOrPut(meta) {
|
||||
val type by meta.string { error("Port type is not defined") }
|
||||
val factory = portFactories.values.firstOrNull { it.type == type }
|
||||
?: error("Port factory for type $type not found")
|
||||
factory.build(context, meta)
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<Ports> {
|
||||
|
||||
override val tag: PluginTag = PluginTag("controls.ports", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override val type: KClass<out Ports> = Ports::class
|
||||
|
||||
override fun build(context: Context, meta: Meta): Ports = Ports()
|
||||
|
||||
}
|
||||
}
|
@ -8,27 +8,35 @@ import kotlinx.coroutines.sync.withLock
|
||||
/**
|
||||
* A port handler for synchronous (request-response) communication with a port. Only one request could be active at a time (others are suspended.
|
||||
* The handler does not guarantee exclusive access to the port so the user mush ensure that no other controller handles port at the moment.
|
||||
*
|
||||
*/
|
||||
public class SynchronousPortHandler(public val port: Port) {
|
||||
private val mutex = Mutex()
|
||||
|
||||
public class SynchronousPort(public val port: Port, private val mutex: Mutex) : Port by port {
|
||||
/**
|
||||
* Send a single message and wait for the flow of respond messages.
|
||||
*/
|
||||
public suspend fun <R> respond(data: ByteArray, transform: suspend Flow<ByteArray>.() -> R): R {
|
||||
return mutex.withLock {
|
||||
public suspend fun <R> respond(data: ByteArray, transform: suspend Flow<ByteArray>.() -> R): R = mutex.withLock {
|
||||
port.send(data)
|
||||
transform(port.receiving())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a synchronous wrapper for a port
|
||||
*/
|
||||
public fun Port.synchronous(mutex: Mutex = Mutex()): SynchronousPort = SynchronousPort(this, mutex)
|
||||
|
||||
/**
|
||||
* Send request and read incoming data blocks until the delimiter is encountered
|
||||
*/
|
||||
public suspend fun SynchronousPortHandler.respondWithDelimiter(data: ByteArray, delimiter: ByteArray): ByteArray {
|
||||
return respond(data) {
|
||||
public suspend fun SynchronousPort.respondWithDelimiter(
|
||||
data: ByteArray,
|
||||
delimiter: ByteArray,
|
||||
): ByteArray = respond(data) {
|
||||
withDelimiter(delimiter).first()
|
||||
}
|
||||
|
||||
public suspend fun SynchronousPort.respondStringWithDelimiter(
|
||||
data: String,
|
||||
delimiter: String,
|
||||
): String = respond(data.encodeToByteArray()) {
|
||||
withStringDelimiter(delimiter).first()
|
||||
}
|
@ -40,12 +40,11 @@ public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray): Flow<ByteArray>
|
||||
/**
|
||||
* Transform byte fragments into utf-8 phrases using utf-8 delimiter
|
||||
*/
|
||||
public fun Flow<ByteArray>.withDelimiter(delimiter: String): Flow<String> {
|
||||
public fun Flow<ByteArray>.withStringDelimiter(delimiter: String): Flow<String> {
|
||||
return withDelimiter(delimiter.encodeToByteArray()).map { it.decodeToString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow of delimited phrases
|
||||
*/
|
||||
public suspend fun Port.delimitedIncoming(delimiter: ByteArray): Flow<ByteArray> =
|
||||
receiving().withDelimiter(delimiter)
|
||||
public fun Port.delimitedIncoming(delimiter: ByteArray): Flow<ByteArray> = receiving().withDelimiter(delimiter)
|
||||
|
@ -17,10 +17,17 @@ import kotlin.coroutines.CoroutineContext
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
override val context: Context = Global,
|
||||
override val meta: Meta = Meta.EMPTY
|
||||
override val meta: Meta = Meta.EMPTY,
|
||||
) : Device {
|
||||
|
||||
/**
|
||||
* Collection of property specifications
|
||||
*/
|
||||
public abstract val properties: Map<String, DevicePropertySpec<D, *>>
|
||||
|
||||
/**
|
||||
* Collection of action specifications
|
||||
*/
|
||||
public abstract val actions: Map<String, DeviceActionSpec<D, *, *>>
|
||||
|
||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
||||
@ -33,6 +40,9 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical state store
|
||||
*/
|
||||
private val logicalState: HashMap<String, Meta?> = HashMap()
|
||||
|
||||
private val sharedMessageFlow: MutableSharedFlow<DeviceMessage> = MutableSharedFlow()
|
||||
@ -99,14 +109,18 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
actions[action]?.executeWithMeta(self, argument)
|
||||
|
||||
/**
|
||||
* Read typed value and update/push event if needed
|
||||
* Read typed value and update/push event if needed.
|
||||
* Return null if property read is not successful or property is undefined.
|
||||
*/
|
||||
public suspend fun <T> DevicePropertySpec<D, T>.read(): T {
|
||||
val res = read(self)
|
||||
public suspend fun <T> DevicePropertySpec<D, T>.readOrNull(): T? {
|
||||
val res = read(self) ?: return null
|
||||
updateLogical(name, converter.objectToMeta(res))
|
||||
return res
|
||||
}
|
||||
|
||||
public suspend fun <T> DevicePropertySpec<D, T>.read(): T =
|
||||
readOrNull() ?: error("Failed to read property $name state")
|
||||
|
||||
public fun <T> DevicePropertySpec<D, T>.get(): T? = getProperty(name)?.let(converter::metaToObject)
|
||||
|
||||
/**
|
||||
@ -121,6 +135,13 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset logical state of a property
|
||||
*/
|
||||
public suspend fun DevicePropertySpec<D, *>.invalidate() {
|
||||
invalidate(name)
|
||||
}
|
||||
|
||||
public suspend operator fun <I, O> DeviceActionSpec<D, I, O>.invoke(input: I? = null): O? = execute(self, input)
|
||||
|
||||
}
|
||||
@ -132,7 +153,7 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||
public val spec: DeviceSpec<in D>,
|
||||
context: Context = Global,
|
||||
meta: Meta = Meta.EMPTY
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : DeviceBase<D>(context, meta) {
|
||||
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
||||
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
||||
|
@ -35,7 +35,7 @@ public interface DevicePropertySpec<in D : Device, T> {
|
||||
* Read physical value from the given [device]
|
||||
*/
|
||||
@InternalDeviceAPI
|
||||
public suspend fun read(device: D): T
|
||||
public suspend fun read(device: D): T?
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,8 +44,8 @@ public interface DevicePropertySpec<in D : Device, T> {
|
||||
public val DevicePropertySpec<*, *>.name: String get() = descriptor.name
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta =
|
||||
converter.objectToMeta(read(device))
|
||||
public suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta? =
|
||||
read(device)?.let(converter::objectToMeta)
|
||||
|
||||
|
||||
public interface WritableDevicePropertySpec<in D : Device, T> : DevicePropertySpec<D, T> {
|
||||
|
@ -31,18 +31,19 @@ public abstract class DeviceSpec<D : Device> {
|
||||
}
|
||||
|
||||
|
||||
public fun <T : Any, P : DevicePropertySpec<D, T>> registerProperty(deviceProperty: P): P {
|
||||
public fun <T, P : DevicePropertySpec<D, T>> registerProperty(deviceProperty: P): P {
|
||||
_properties[deviceProperty.name] = deviceProperty
|
||||
return deviceProperty
|
||||
}
|
||||
|
||||
public fun <T : Any> registerProperty(
|
||||
public fun <T> registerProperty(
|
||||
converter: MetaConverter<T>,
|
||||
readOnlyProperty: KProperty1<D, T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): DevicePropertySpec<D, T> {
|
||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(readOnlyProperty.name).apply(descriptorBuilder)
|
||||
override val descriptor: PropertyDescriptor =
|
||||
PropertyDescriptor(readOnlyProperty.name).apply(descriptorBuilder)
|
||||
override val converter: MetaConverter<T> = converter
|
||||
override suspend fun read(device: D): T =
|
||||
withContext(device.coroutineContext) { readOnlyProperty.get(device) }
|
||||
@ -50,10 +51,10 @@ public abstract class DeviceSpec<D : Device> {
|
||||
return registerProperty(deviceProperty)
|
||||
}
|
||||
|
||||
public fun <T : Any> property(
|
||||
public fun <T> property(
|
||||
converter: MetaConverter<T>,
|
||||
readOnlyProperty: KProperty1<D, T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, DevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _, property ->
|
||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||
@ -74,10 +75,10 @@ public abstract class DeviceSpec<D : Device> {
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> mutableProperty(
|
||||
public fun <T> mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
readWriteProperty: KMutableProperty1<D, T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _, property ->
|
||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||
@ -103,11 +104,11 @@ public abstract class DeviceSpec<D : Device> {
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> property(
|
||||
public fun <T> property(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> T
|
||||
read: suspend D.() -> T?,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||
val propertyName = name ?: property.name
|
||||
@ -115,7 +116,7 @@ public abstract class DeviceSpec<D : Device> {
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T = withContext(device.coroutineContext) { device.read() }
|
||||
override suspend fun read(device: D): T? = withContext(device.coroutineContext) { device.read() }
|
||||
}
|
||||
registerProperty(deviceProperty)
|
||||
ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>> { _, _ ->
|
||||
@ -123,12 +124,12 @@ public abstract class DeviceSpec<D : Device> {
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> mutableProperty(
|
||||
public fun <T> mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> T,
|
||||
write: suspend D.(T) -> Unit
|
||||
read: suspend D.() -> T?,
|
||||
write: suspend D.(T) -> Unit,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
||||
val propertyName = name ?: property.name
|
||||
@ -136,7 +137,7 @@ public abstract class DeviceSpec<D : Device> {
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
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): Unit = withContext(device.coroutineContext) {
|
||||
device.write(value)
|
||||
@ -149,17 +150,17 @@ public abstract class DeviceSpec<D : Device> {
|
||||
}
|
||||
|
||||
|
||||
public fun <I : Any, O : Any> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O> {
|
||||
public fun <I, O> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O> {
|
||||
_actions[deviceAction.name] = deviceAction
|
||||
return deviceAction
|
||||
}
|
||||
|
||||
public fun <I : Any, O : Any> action(
|
||||
public fun <I, O> action(
|
||||
inputConverter: MetaConverter<I>,
|
||||
outputConverter: MetaConverter<O>,
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(I?) -> O?
|
||||
execute: suspend D.(I?) -> O?,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>>> =
|
||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||
val actionName = name ?: property.name
|
||||
@ -185,8 +186,9 @@ public abstract class DeviceSpec<D : Device> {
|
||||
public fun metaAction(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(Meta?) -> Meta?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> = action(
|
||||
execute: suspend D.(Meta?) -> Meta?,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
|
||||
action(
|
||||
MetaConverter.Companion.meta,
|
||||
MetaConverter.Companion.meta,
|
||||
descriptorBuilder,
|
||||
@ -201,8 +203,9 @@ public abstract class DeviceSpec<D : Device> {
|
||||
public fun unitAction(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.() -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> = action(
|
||||
execute: suspend D.() -> Unit,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
|
||||
action(
|
||||
MetaConverter.Companion.meta,
|
||||
MetaConverter.Companion.meta,
|
||||
descriptorBuilder,
|
||||
@ -212,3 +215,35 @@ public abstract class DeviceSpec<D : Device> {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a mutable logical property for a device
|
||||
*/
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.logicalProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _, property ->
|
||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||
val propertyName = name ?: property.name
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply {
|
||||
//TODO add type from converter
|
||||
writable = true
|
||||
}.apply(descriptorBuilder)
|
||||
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T? =
|
||||
device.getProperty(propertyName)?.let(converter::metaToObject)
|
||||
|
||||
override suspend fun write(device: D, value: T): Unit =
|
||||
device.writeProperty(propertyName, converter.objectToMeta(value))
|
||||
}
|
||||
registerProperty(deviceProperty)
|
||||
ReadOnlyProperty { _, _ ->
|
||||
deviceProperty
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import kotlin.properties.ReadOnlyProperty
|
||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Boolean
|
||||
read: suspend D.() -> Boolean?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
|
||||
MetaConverter.boolean,
|
||||
{
|
||||
@ -38,7 +38,7 @@ private inline fun numberDescriptor(
|
||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
||||
name: String? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
read: suspend D.() -> Number
|
||||
read: suspend D.() -> Number?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> = property(
|
||||
MetaConverter.number,
|
||||
numberDescriptor(descriptorBuilder),
|
||||
@ -49,7 +49,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Double
|
||||
read: suspend D.() -> Double?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> = property(
|
||||
MetaConverter.double,
|
||||
numberDescriptor(descriptorBuilder),
|
||||
@ -60,7 +60,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> String
|
||||
read: suspend D.() -> String?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
|
||||
MetaConverter.string,
|
||||
{
|
||||
@ -76,7 +76,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Meta
|
||||
read: suspend D.() -> Meta?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
|
||||
MetaConverter.meta,
|
||||
{
|
||||
@ -94,7 +94,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Boolean,
|
||||
read: suspend D.() -> Boolean?,
|
||||
write: suspend D.(Boolean) -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
||||
mutableProperty(
|
||||
|
@ -58,7 +58,7 @@ public class TcpPort private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun write(data: ByteArray) {
|
||||
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO){
|
||||
futureChannel.await().write(ByteBuffer.wrap(data))
|
||||
}
|
||||
|
||||
@ -74,6 +74,9 @@ public class TcpPort private constructor(
|
||||
}
|
||||
|
||||
public companion object : PortFactory {
|
||||
|
||||
override val type: String = "tcp"
|
||||
|
||||
public fun open(
|
||||
context: Context,
|
||||
host: String,
|
||||
|
@ -0,0 +1,30 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import space.kscience.dataforge.context.AbstractPlugin
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class TcpPortPlugin : AbstractPlugin() {
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = when(target){
|
||||
PortFactory.TYPE -> mapOf(Name.EMPTY to TcpPort)
|
||||
else -> emptyMap()
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<TcpPortPlugin> {
|
||||
|
||||
override val tag: PluginTag = PluginTag("controls.ports.tcp", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override val type: KClass<out TcpPortPlugin> = TcpPortPlugin::class
|
||||
|
||||
override fun build(context: Context, meta: Meta): TcpPortPlugin = TcpPortPlugin()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -7,4 +7,4 @@ import kotlinx.coroutines.runBlocking
|
||||
*/
|
||||
public operator fun <D : DeviceBase<D>, T : Any> D.get(
|
||||
propertySpec: DevicePropertySpec<D, T>
|
||||
): T = runBlocking { read(propertySpec) }
|
||||
): T? = runBlocking { read(propertySpec) }
|
@ -5,6 +5,6 @@ plugins {
|
||||
val ktorVersion: String by rootProject.extra
|
||||
|
||||
dependencies {
|
||||
api(project(":controls-core"))
|
||||
api(projects.controlsCore)
|
||||
api("io.ktor:ktor-network:$ktorVersion")
|
||||
}
|
@ -56,6 +56,9 @@ public class KtorTcpPort internal constructor(
|
||||
}
|
||||
|
||||
public companion object : PortFactory {
|
||||
|
||||
override val type: String = "tcp"
|
||||
|
||||
public fun open(
|
||||
context: Context,
|
||||
host: String,
|
@ -0,0 +1,30 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import space.kscience.dataforge.context.AbstractPlugin
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class KtorTcpPortPlugin : AbstractPlugin() {
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = when(target){
|
||||
PortFactory.TYPE -> mapOf(Name.EMPTY to KtorTcpPort)
|
||||
else -> emptyMap()
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<KtorTcpPortPlugin> {
|
||||
|
||||
override val tag: PluginTag = PluginTag("controls.ports.serial", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override val type: KClass<out KtorTcpPortPlugin> = KtorTcpPortPlugin::class
|
||||
|
||||
override fun build(context: Context, meta: Meta): KtorTcpPortPlugin = KtorTcpPortPlugin()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -58,6 +58,9 @@ public class SerialPort private constructor(
|
||||
|
||||
public companion object : PortFactory {
|
||||
|
||||
override val type: String = "com"
|
||||
|
||||
|
||||
/**
|
||||
* Construct ComPort with given parameters
|
||||
*/
|
||||
|
@ -0,0 +1,31 @@
|
||||
package space.kscience.controls.serial
|
||||
|
||||
import space.kscience.controls.ports.PortFactory
|
||||
import space.kscience.dataforge.context.AbstractPlugin
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class SerialPortPlugin : AbstractPlugin() {
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = when(target){
|
||||
PortFactory.TYPE -> mapOf(Name.EMPTY to SerialPort)
|
||||
else -> emptyMap()
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<SerialPortPlugin> {
|
||||
|
||||
override val tag: PluginTag = PluginTag("controls.ports.serial", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override val type: KClass<out SerialPortPlugin> = SerialPortPlugin::class
|
||||
|
||||
override fun build(context: Context, meta: Meta): SerialPortPlugin = SerialPortPlugin()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,7 @@ val ktorVersion: String by rootProject.extra
|
||||
|
||||
dependencies {
|
||||
implementation(project(":controls-core"))
|
||||
implementation(project(":controls-tcp"))
|
||||
implementation(project(":controls-ktor-tcp"))
|
||||
implementation(projects.magix.magixServer)
|
||||
implementation("io.ktor:ktor-server-cio:$ktorVersion")
|
||||
implementation("io.ktor:ktor-server-websockets:$ktorVersion")
|
||||
|
21
demo/mks-pdr900/build.gradle.kts
Normal file
21
demo/mks-pdr900/build.gradle.kts
Normal file
@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
application
|
||||
}
|
||||
|
||||
//TODO to be moved to a separate project
|
||||
//
|
||||
//application{
|
||||
// mainClass.set("ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt")
|
||||
//}
|
||||
|
||||
kotlin{
|
||||
explicitApi = null
|
||||
}
|
||||
|
||||
val ktorVersion: String by rootProject.extra
|
||||
val dataforgeVersion: String by extra
|
||||
|
||||
dependencies {
|
||||
implementation(projects.controlsKtorTcp)
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package center.sciprog.devices.mks
|
||||
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import space.kscience.controls.ports.Ports
|
||||
import space.kscience.controls.ports.SynchronousPort
|
||||
import space.kscience.controls.ports.respondStringWithDelimiter
|
||||
import space.kscience.controls.ports.synchronous
|
||||
import space.kscience.controls.spec.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Factory
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
|
||||
class MksPdr900Device(context: Context, meta: Meta) : DeviceBySpec<MksPdr900Device>(MksPdr900Device, context, meta) {
|
||||
|
||||
private val portDelegate = lazy {
|
||||
val ports = context.request(Ports)
|
||||
ports.buildPort(meta["port"] ?: error("Port is not defined in device configuration")).synchronous()
|
||||
}
|
||||
|
||||
private val port: SynchronousPort by portDelegate
|
||||
|
||||
private val address by meta.int(253)
|
||||
private val channel by meta.int(5)
|
||||
|
||||
private val responsePattern: Regex by lazy {
|
||||
("@${address}ACK(.*);FF").toRegex()
|
||||
}
|
||||
|
||||
private suspend fun talk(requestContent: String): String? = withTimeoutOrNull(5000) {
|
||||
val answer = port.respondStringWithDelimiter(String.format("@%s%s;FF", address, requestContent), ";FF")
|
||||
responsePattern.matchEntire(answer)?.groups?.get(1)?.value
|
||||
?: error("Message $answer does not match $responsePattern")
|
||||
}
|
||||
|
||||
public suspend fun readPowerOn(): Boolean = when (val answer = talk("FP?")) {
|
||||
"ON" -> true
|
||||
"OFF" -> false
|
||||
else -> error("Unknown answer for 'FP?': $answer")
|
||||
}
|
||||
|
||||
|
||||
public suspend fun writePowerOn(powerOnValue: Boolean) {
|
||||
error.invalidate()
|
||||
if (powerOnValue) {
|
||||
val ans = talk("FP!ON")
|
||||
if (ans == "ON") {
|
||||
updateLogical(powerOn, true)
|
||||
} else {
|
||||
updateLogical(error, "Failed to set power state")
|
||||
}
|
||||
} else {
|
||||
val ans = talk("FP!OFF")
|
||||
if (ans == "OFF") {
|
||||
updateLogical(powerOn, false)
|
||||
} else {
|
||||
updateLogical(error, "Failed to set power state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun readChannelData(): Double? {
|
||||
val answer: String? = talk("PR$channel?")
|
||||
error.invalidate()
|
||||
return if (answer.isNullOrEmpty()) {
|
||||
// updateState(PortSensor.CONNECTED_STATE, false)
|
||||
updateLogical(error, "No connection")
|
||||
null
|
||||
} else {
|
||||
val res = answer.toDouble()
|
||||
if (res <= 0) {
|
||||
updateLogical(powerOn, false)
|
||||
updateLogical(error, "No power")
|
||||
null
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object : DeviceSpec<MksPdr900Device>(), Factory<MksPdr900Device> {
|
||||
override fun build(context: Context, meta: Meta): MksPdr900Device = MksPdr900Device(context, meta)
|
||||
|
||||
|
||||
val powerOn by booleanProperty(read = MksPdr900Device::readPowerOn, write = MksPdr900Device::writePowerOn)
|
||||
|
||||
val value by doubleProperty(read = MksPdr900Device::readChannelData)
|
||||
|
||||
val error by logicalProperty(MetaConverter.string)
|
||||
|
||||
|
||||
override fun MksPdr900Device.onClose() {
|
||||
if (portDelegate.isInitialized()) {
|
||||
port.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package center.sciprog.devices.mks
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
|
||||
object NullableStringMetaConverter : MetaConverter<String?> {
|
||||
override fun metaToObject(meta: Meta): String? = meta.string
|
||||
override fun objectToMeta(obj: String?): Meta = Meta {}
|
||||
}
|
@ -23,7 +23,7 @@ val ktorVersion: String by rootProject.extra
|
||||
val dataforgeVersion: String by extra
|
||||
|
||||
dependencies {
|
||||
implementation(project(":controls-tcp"))
|
||||
implementation(project(":controls-ktor-tcp"))
|
||||
implementation(project(":controls-magix-client"))
|
||||
implementation("no.tornado:tornadofx:1.7.20")
|
||||
}
|
||||
|
@ -44,8 +44,8 @@ fun VBox.piMotionMasterAxis(
|
||||
label(axisName)
|
||||
coroutineScope.launch {
|
||||
with(axis) {
|
||||
val min = minPosition.read()
|
||||
val max = maxPosition.read()
|
||||
val min: Double = minPosition.read()
|
||||
val max: Double = maxPosition.read()
|
||||
val positionProperty = fxProperty(position)
|
||||
val startPosition = position.read()
|
||||
runLater {
|
||||
|
@ -87,7 +87,7 @@ class PiMotionMasterDevice(
|
||||
suspend fun getErrorCode(): Int = mutex.withLock {
|
||||
withTimeout(timeoutValue) {
|
||||
sendCommandInternal("ERR?")
|
||||
val errorString = port?.receiving()?.withDelimiter("\n")?.first() ?: error("Not connected to device")
|
||||
val errorString = port?.receiving()?.withStringDelimiter("\n")?.first() ?: error("Not connected to device")
|
||||
errorString.trim().toInt()
|
||||
}
|
||||
}
|
||||
@ -100,7 +100,7 @@ class PiMotionMasterDevice(
|
||||
try {
|
||||
withTimeout(timeoutValue) {
|
||||
sendCommandInternal(command, *arguments)
|
||||
val phrases = port?.receiving()?.withDelimiter("\n") ?: error("Not connected to device")
|
||||
val phrases = port?.receiving()?.withStringDelimiter("\n") ?: error("Not connected to device")
|
||||
phrases.transformWhile { line ->
|
||||
emit(line)
|
||||
line.endsWith(" \n")
|
||||
|
@ -42,7 +42,7 @@ dependencyResolutionManagement {
|
||||
|
||||
include(
|
||||
":controls-core",
|
||||
":controls-tcp",
|
||||
":controls-ktor-tcp",
|
||||
":controls-serial",
|
||||
":controls-server",
|
||||
":controls-opcua",
|
||||
@ -64,5 +64,6 @@ include(
|
||||
":demo:magix-demo",
|
||||
":demo:car",
|
||||
":demo:motors",
|
||||
":demo:echo"
|
||||
":demo:echo",
|
||||
":demo:mks-pdr900"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user