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].
|
* 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)
|
public suspend fun writeProperty(propertyName: String, value: Meta)
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ public suspend fun Device.getOrReadProperty(propertyName: String): Meta =
|
|||||||
*
|
*
|
||||||
* TODO currently this
|
* TODO currently this
|
||||||
*/
|
*/
|
||||||
public fun Device.getProperties(): Meta = Meta {
|
public fun Device.getAllProperties(): Meta = Meta {
|
||||||
for (descriptor in propertyDescriptors) {
|
for (descriptor in propertyDescriptors) {
|
||||||
setMeta(Name.parse(descriptor.name), getProperty(descriptor.name))
|
setMeta(Name.parse(descriptor.name), getProperty(descriptor.name))
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic bi-directional sender/receiver object
|
* A generic bidirectional sender/receiver object
|
||||||
*/
|
*/
|
||||||
public interface Socket<T> : Closeable {
|
public interface Socket<T> : Closeable {
|
||||||
/**
|
/**
|
||||||
|
@ -6,11 +6,19 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import space.kscience.controls.api.Socket
|
import space.kscience.controls.api.Socket
|
||||||
import space.kscience.dataforge.context.*
|
import space.kscience.dataforge.context.*
|
||||||
|
import space.kscience.dataforge.misc.Type
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
public interface Port : ContextAware, Socket<ByteArray>
|
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(
|
public abstract class AbstractPort(
|
||||||
override val context: Context,
|
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.
|
* 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.
|
* For example [delimitedIncoming] generates phrases with fixed delimiter.
|
||||||
*/
|
*/
|
||||||
override fun receiving(): Flow<ByteArray> {
|
override fun receiving(): Flow<ByteArray> = incoming.receiveAsFlow()
|
||||||
return incoming.receiveAsFlow()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
outgoing.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.
|
* 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.
|
* 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) {
|
public class SynchronousPort(public val port: Port, private val mutex: Mutex) : Port by port {
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a single message and wait for the flow of respond messages.
|
* 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 {
|
public suspend fun <R> respond(data: ByteArray, transform: suspend Flow<ByteArray>.() -> R): R = mutex.withLock {
|
||||||
return mutex.withLock {
|
|
||||||
port.send(data)
|
port.send(data)
|
||||||
transform(port.receiving())
|
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
|
* Send request and read incoming data blocks until the delimiter is encountered
|
||||||
*/
|
*/
|
||||||
public suspend fun SynchronousPortHandler.respondWithDelimiter(data: ByteArray, delimiter: ByteArray): ByteArray {
|
public suspend fun SynchronousPort.respondWithDelimiter(
|
||||||
return respond(data) {
|
data: ByteArray,
|
||||||
|
delimiter: ByteArray,
|
||||||
|
): ByteArray = respond(data) {
|
||||||
withDelimiter(delimiter).first()
|
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
|
* 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() }
|
return withDelimiter(delimiter.encodeToByteArray()).map { it.decodeToString() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flow of delimited phrases
|
* A flow of delimited phrases
|
||||||
*/
|
*/
|
||||||
public suspend fun Port.delimitedIncoming(delimiter: ByteArray): Flow<ByteArray> =
|
public fun Port.delimitedIncoming(delimiter: ByteArray): Flow<ByteArray> = receiving().withDelimiter(delimiter)
|
||||||
receiving().withDelimiter(delimiter)
|
|
||||||
|
@ -17,10 +17,17 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public abstract class DeviceBase<D : DeviceBase<D>>(
|
public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||||
override val context: Context = Global,
|
override val context: Context = Global,
|
||||||
override val meta: Meta = Meta.EMPTY
|
override val meta: Meta = Meta.EMPTY,
|
||||||
) : Device {
|
) : Device {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of property specifications
|
||||||
|
*/
|
||||||
public abstract val properties: Map<String, DevicePropertySpec<D, *>>
|
public abstract val properties: Map<String, DevicePropertySpec<D, *>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of action specifications
|
||||||
|
*/
|
||||||
public abstract val actions: Map<String, DeviceActionSpec<D, *, *>>
|
public abstract val actions: Map<String, DeviceActionSpec<D, *, *>>
|
||||||
|
|
||||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
override val propertyDescriptors: Collection<PropertyDescriptor>
|
||||||
@ -33,6 +40,9 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
|||||||
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logical state store
|
||||||
|
*/
|
||||||
private val logicalState: HashMap<String, Meta?> = HashMap()
|
private val logicalState: HashMap<String, Meta?> = HashMap()
|
||||||
|
|
||||||
private val sharedMessageFlow: MutableSharedFlow<DeviceMessage> = MutableSharedFlow()
|
private val sharedMessageFlow: MutableSharedFlow<DeviceMessage> = MutableSharedFlow()
|
||||||
@ -99,14 +109,18 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
|||||||
actions[action]?.executeWithMeta(self, argument)
|
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 {
|
public suspend fun <T> DevicePropertySpec<D, T>.readOrNull(): T? {
|
||||||
val res = read(self)
|
val res = read(self) ?: return null
|
||||||
updateLogical(name, converter.objectToMeta(res))
|
updateLogical(name, converter.objectToMeta(res))
|
||||||
return 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)
|
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)
|
public suspend operator fun <I, O> DeviceActionSpec<D, I, O>.invoke(input: I? = null): O? = execute(self, input)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -132,17 +153,17 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
|||||||
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||||
public val spec: DeviceSpec<in D>,
|
public val spec: DeviceSpec<in D>,
|
||||||
context: Context = Global,
|
context: Context = Global,
|
||||||
meta: Meta = Meta.EMPTY
|
meta: Meta = Meta.EMPTY,
|
||||||
) : DeviceBase<D>(context, meta) {
|
) : DeviceBase<D>(context, meta) {
|
||||||
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
||||||
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
||||||
|
|
||||||
override suspend fun open(): Unit = with(spec){
|
override suspend fun open(): Unit = with(spec) {
|
||||||
super.open()
|
super.open()
|
||||||
self.onOpen()
|
self.onOpen()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close(): Unit = with(spec){
|
override fun close(): Unit = with(spec) {
|
||||||
self.onClose()
|
self.onClose()
|
||||||
super.close()
|
super.close()
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ public interface DevicePropertySpec<in D : Device, T> {
|
|||||||
* Read physical value from the given [device]
|
* Read physical value from the given [device]
|
||||||
*/
|
*/
|
||||||
@InternalDeviceAPI
|
@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
|
public val DevicePropertySpec<*, *>.name: String get() = descriptor.name
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta =
|
public suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta? =
|
||||||
converter.objectToMeta(read(device))
|
read(device)?.let(converter::objectToMeta)
|
||||||
|
|
||||||
|
|
||||||
public interface WritableDevicePropertySpec<in D : Device, T> : DevicePropertySpec<D, T> {
|
public interface WritableDevicePropertySpec<in D : Device, T> : DevicePropertySpec<D, T> {
|
||||||
|
@ -24,25 +24,26 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
public val actions: Map<String, DeviceActionSpec<D, *, *>> get() = _actions
|
public val actions: Map<String, DeviceActionSpec<D, *, *>> get() = _actions
|
||||||
|
|
||||||
|
|
||||||
public open suspend fun D.onOpen(){
|
public open suspend fun D.onOpen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public open fun D.onClose(){
|
public open fun D.onClose() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
_properties[deviceProperty.name] = deviceProperty
|
||||||
return deviceProperty
|
return deviceProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> registerProperty(
|
public fun <T> registerProperty(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
readOnlyProperty: KProperty1<D, T>,
|
readOnlyProperty: KProperty1<D, T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
): DevicePropertySpec<D, T> {
|
): DevicePropertySpec<D, T> {
|
||||||
val deviceProperty = object : 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 val converter: MetaConverter<T> = converter
|
||||||
override suspend fun read(device: D): T =
|
override suspend fun read(device: D): T =
|
||||||
withContext(device.coroutineContext) { readOnlyProperty.get(device) }
|
withContext(device.coroutineContext) { readOnlyProperty.get(device) }
|
||||||
@ -50,11 +51,11 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
return registerProperty(deviceProperty)
|
return registerProperty(deviceProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> property(
|
public fun <T> property(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
readOnlyProperty: KProperty1<D, T>,
|
readOnlyProperty: KProperty1<D, T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?,DevicePropertySpec<D, T>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, DevicePropertySpec<D, T>>> =
|
||||||
PropertyDelegateProvider { _, property ->
|
PropertyDelegateProvider { _, property ->
|
||||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
||||||
@ -74,10 +75,10 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> mutableProperty(
|
public fun <T> mutableProperty(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
readWriteProperty: KMutableProperty1<D, T>,
|
readWriteProperty: KMutableProperty1<D, T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
||||||
PropertyDelegateProvider { _, property ->
|
PropertyDelegateProvider { _, property ->
|
||||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
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>,
|
converter: MetaConverter<T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> T
|
read: suspend D.() -> T?,
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
|
||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||||
val propertyName = name ?: property.name
|
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 descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||||
override val converter: MetaConverter<T> = converter
|
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)
|
registerProperty(deviceProperty)
|
||||||
ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>> { _, _ ->
|
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>,
|
converter: MetaConverter<T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> T,
|
read: suspend D.() -> T?,
|
||||||
write: suspend D.(T) -> Unit
|
write: suspend D.(T) -> Unit,
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>>> =
|
||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
||||||
val propertyName = name ?: property.name
|
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 descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||||
override val converter: MetaConverter<T> = converter
|
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) {
|
override suspend fun write(device: D, value: T): Unit = withContext(device.coroutineContext) {
|
||||||
device.write(value)
|
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
|
_actions[deviceAction.name] = deviceAction
|
||||||
return deviceAction
|
return deviceAction
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <I : Any, O : Any> action(
|
public fun <I, O> action(
|
||||||
inputConverter: MetaConverter<I>,
|
inputConverter: MetaConverter<I>,
|
||||||
outputConverter: MetaConverter<O>,
|
outputConverter: MetaConverter<O>,
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
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>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>>> =
|
||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
||||||
val actionName = name ?: property.name
|
val actionName = name ?: property.name
|
||||||
@ -185,13 +186,14 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
public fun metaAction(
|
public fun metaAction(
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
execute: suspend D.(Meta?) -> Meta?
|
execute: suspend D.(Meta?) -> Meta?,
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> = action(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
|
||||||
|
action(
|
||||||
MetaConverter.Companion.meta,
|
MetaConverter.Companion.meta,
|
||||||
MetaConverter.Companion.meta,
|
MetaConverter.Companion.meta,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
name
|
name
|
||||||
){
|
) {
|
||||||
execute(it)
|
execute(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,14 +203,47 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
public fun unitAction(
|
public fun unitAction(
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
execute: suspend D.() -> Unit
|
execute: suspend D.() -> Unit,
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> = action(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
|
||||||
|
action(
|
||||||
MetaConverter.Companion.meta,
|
MetaConverter.Companion.meta,
|
||||||
MetaConverter.Companion.meta,
|
MetaConverter.Companion.meta,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
name
|
name
|
||||||
){
|
) {
|
||||||
execute()
|
execute()
|
||||||
null
|
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(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Boolean
|
read: suspend D.() -> Boolean?
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
|
||||||
MetaConverter.boolean,
|
MetaConverter.boolean,
|
||||||
{
|
{
|
||||||
@ -38,7 +38,7 @@ private inline fun numberDescriptor(
|
|||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
read: suspend D.() -> Number
|
read: suspend D.() -> Number?
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> = property(
|
||||||
MetaConverter.number,
|
MetaConverter.number,
|
||||||
numberDescriptor(descriptorBuilder),
|
numberDescriptor(descriptorBuilder),
|
||||||
@ -49,7 +49,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
|||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Double
|
read: suspend D.() -> Double?
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> = property(
|
||||||
MetaConverter.double,
|
MetaConverter.double,
|
||||||
numberDescriptor(descriptorBuilder),
|
numberDescriptor(descriptorBuilder),
|
||||||
@ -60,7 +60,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
|||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> String
|
read: suspend D.() -> String?
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
|
||||||
MetaConverter.string,
|
MetaConverter.string,
|
||||||
{
|
{
|
||||||
@ -76,7 +76,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
|||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Meta
|
read: suspend D.() -> Meta?
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
|
||||||
MetaConverter.meta,
|
MetaConverter.meta,
|
||||||
{
|
{
|
||||||
@ -94,7 +94,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
|||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Boolean,
|
read: suspend D.() -> Boolean?,
|
||||||
write: suspend D.(Boolean) -> Unit
|
write: suspend D.(Boolean) -> Unit
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
||||||
mutableProperty(
|
mutableProperty(
|
||||||
|
@ -52,20 +52,20 @@ public class TcpPort private constructor(
|
|||||||
}
|
}
|
||||||
if (num < 0) cancel("The input channel is exhausted")
|
if (num < 0) cancel("The input channel is exhausted")
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
logger.error(ex){"Channel read error"}
|
logger.error(ex) { "Channel read error" }
|
||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun write(data: ByteArray) {
|
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO){
|
||||||
futureChannel.await().write(ByteBuffer.wrap(data))
|
futureChannel.await().write(ByteBuffer.wrap(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
override fun close() {
|
override fun close() {
|
||||||
listenerJob.cancel()
|
listenerJob.cancel()
|
||||||
if(futureChannel.isCompleted){
|
if (futureChannel.isCompleted) {
|
||||||
futureChannel.getCompleted().close()
|
futureChannel.getCompleted().close()
|
||||||
} else {
|
} else {
|
||||||
futureChannel.cancel()
|
futureChannel.cancel()
|
||||||
@ -74,6 +74,9 @@ public class TcpPort private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public companion object : PortFactory {
|
public companion object : PortFactory {
|
||||||
|
|
||||||
|
override val type: String = "tcp"
|
||||||
|
|
||||||
public fun open(
|
public fun open(
|
||||||
context: Context,
|
context: Context,
|
||||||
host: String,
|
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(
|
public operator fun <D : DeviceBase<D>, T : Any> D.get(
|
||||||
propertySpec: DevicePropertySpec<D, T>
|
propertySpec: DevicePropertySpec<D, T>
|
||||||
): T = runBlocking { read(propertySpec) }
|
): T? = runBlocking { read(propertySpec) }
|
@ -5,6 +5,6 @@ plugins {
|
|||||||
val ktorVersion: String by rootProject.extra
|
val ktorVersion: String by rootProject.extra
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":controls-core"))
|
api(projects.controlsCore)
|
||||||
api("io.ktor:ktor-network:$ktorVersion")
|
api("io.ktor:ktor-network:$ktorVersion")
|
||||||
}
|
}
|
@ -55,7 +55,10 @@ public class KtorTcpPort internal constructor(
|
|||||||
super.close()
|
super.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object: PortFactory {
|
public companion object : PortFactory {
|
||||||
|
|
||||||
|
override val type: String = "tcp"
|
||||||
|
|
||||||
public fun open(
|
public fun open(
|
||||||
context: Context,
|
context: Context,
|
||||||
host: String,
|
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 {
|
public companion object : PortFactory {
|
||||||
|
|
||||||
|
override val type: String = "com"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct ComPort with given parameters
|
* 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 {
|
dependencies {
|
||||||
implementation(project(":controls-core"))
|
implementation(project(":controls-core"))
|
||||||
implementation(project(":controls-tcp"))
|
implementation(project(":controls-ktor-tcp"))
|
||||||
implementation(projects.magix.magixServer)
|
implementation(projects.magix.magixServer)
|
||||||
implementation("io.ktor:ktor-server-cio:$ktorVersion")
|
implementation("io.ktor:ktor-server-cio:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-server-websockets:$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
|
val dataforgeVersion: String by extra
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":controls-tcp"))
|
implementation(project(":controls-ktor-tcp"))
|
||||||
implementation(project(":controls-magix-client"))
|
implementation(project(":controls-magix-client"))
|
||||||
implementation("no.tornado:tornadofx:1.7.20")
|
implementation("no.tornado:tornadofx:1.7.20")
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@ fun VBox.piMotionMasterAxis(
|
|||||||
label(axisName)
|
label(axisName)
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
with(axis) {
|
with(axis) {
|
||||||
val min = minPosition.read()
|
val min: Double = minPosition.read()
|
||||||
val max = maxPosition.read()
|
val max: Double = maxPosition.read()
|
||||||
val positionProperty = fxProperty(position)
|
val positionProperty = fxProperty(position)
|
||||||
val startPosition = position.read()
|
val startPosition = position.read()
|
||||||
runLater {
|
runLater {
|
||||||
|
@ -87,7 +87,7 @@ class PiMotionMasterDevice(
|
|||||||
suspend fun getErrorCode(): Int = mutex.withLock {
|
suspend fun getErrorCode(): Int = mutex.withLock {
|
||||||
withTimeout(timeoutValue) {
|
withTimeout(timeoutValue) {
|
||||||
sendCommandInternal("ERR?")
|
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()
|
errorString.trim().toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ class PiMotionMasterDevice(
|
|||||||
try {
|
try {
|
||||||
withTimeout(timeoutValue) {
|
withTimeout(timeoutValue) {
|
||||||
sendCommandInternal(command, *arguments)
|
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 ->
|
phrases.transformWhile { line ->
|
||||||
emit(line)
|
emit(line)
|
||||||
line.endsWith(" \n")
|
line.endsWith(" \n")
|
||||||
|
@ -42,7 +42,7 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
include(
|
include(
|
||||||
":controls-core",
|
":controls-core",
|
||||||
":controls-tcp",
|
":controls-ktor-tcp",
|
||||||
":controls-serial",
|
":controls-serial",
|
||||||
":controls-server",
|
":controls-server",
|
||||||
":controls-opcua",
|
":controls-opcua",
|
||||||
@ -64,5 +64,6 @@ include(
|
|||||||
":demo:magix-demo",
|
":demo:magix-demo",
|
||||||
":demo:car",
|
":demo:car",
|
||||||
":demo:motors",
|
":demo:motors",
|
||||||
":demo:echo"
|
":demo:echo",
|
||||||
|
":demo:mks-pdr900"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user