Implemented external configuration support through ExternalConfigurationProvider and ExternalConfigApplier, and expanded error handling in AbstractDeviceHubManager with CUSTOM strategy support. Improved DeviceLifecycleConfig, added HealthChecker support for device health checks. Implemented hot-swappable device functionality.

This commit is contained in:
Максим Колпаков 2024-12-25 18:11:01 +03:00
parent 76fa751e25
commit 7e286ca111
4 changed files with 1011 additions and 904 deletions

View File

@ -8,6 +8,11 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
public enum class LifecycleState { public enum class LifecycleState {
/**
* The device is newly created and has not started yet.
*/
INITIAL,
/** /**
* Device is initializing * Device is initializing
*/ */
@ -18,6 +23,11 @@ public enum class LifecycleState {
*/ */
STARTED, STARTED,
/**
* The Device is stopping
*/
STOPPING,
/** /**
* The Device is closed * The Device is closed
*/ */
@ -50,8 +60,10 @@ public interface WithLifeCycle {
public fun WithLifeCycle.bindToDeviceLifecycle(device: Device){ public fun WithLifeCycle.bindToDeviceLifecycle(device: Device){
device.onLifecycleEvent { device.onLifecycleEvent {
when(it){ when(it){
LifecycleState.INITIAL -> {/*ignore*/}
LifecycleState.STARTING -> start() LifecycleState.STARTING -> start()
LifecycleState.STARTED -> {/*ignore*/} LifecycleState.STARTED -> {/*ignore*/}
LifecycleState.STOPPING -> stop()
LifecycleState.STOPPED -> stop() LifecycleState.STOPPED -> stop()
LifecycleState.ERROR -> stop() LifecycleState.ERROR -> stop()
} }

View File

@ -1,384 +1,248 @@
package space.kscience.controls.spec package space.kscience.controls.spec
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import space.kscience.controls.api.ActionDescriptorBuilder import space.kscience.controls.api.*
import space.kscience.controls.api.Device import space.kscience.dataforge.meta.*
import space.kscience.controls.api.PropertyDescriptorBuilder
import space.kscience.controls.api.id
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaConverter
import space.kscience.dataforge.meta.ValueType
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.string
import kotlin.properties.PropertyDelegateProvider import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
/** /**
* Create a [MetaConverter] for enum values * Create a [MetaConverter] for enum values using [reified] type with an option to ignore case.
*/ */
public fun <E : Enum<E>> createEnumConverter(enumValues: Array<E>): MetaConverter<E> = object : MetaConverter<E> { public inline fun <reified E : Enum<E>> createEnumConverter(ignoreCase: Boolean = false): MetaConverter<E> {
val allValues = enumValues<E>()
return object : MetaConverter<E> {
override val descriptor: MetaDescriptor = MetaDescriptor { override val descriptor: MetaDescriptor = MetaDescriptor {
valueType(ValueType.STRING) valueType(ValueType.STRING)
allowedValues(enumValues.map { it.name }) allowedValues(allValues.map { it.name })
} }
override fun readOrNull(source: Meta): E? { override fun readOrNull(source: Meta): E? {
val value = source.value ?: return null val stringVal = source.value?.string ?: return null
return enumValues.firstOrNull { it.name == value.string } return allValues.firstOrNull { it.name.equals(stringVal, ignoreCase) }
} }
override fun convert(obj: E): Meta = Meta(obj.name) override fun convert(obj: E): Meta = Meta(obj.name)
}
} }
/** /**
* A read-only device property that delegates reading to a device [KProperty1] * Unified function: if [write] == null -> read-only property, else -> mutable property.
*/ */
public fun <T, D : Device> CompositeControlComponentSpec<D>.property( public fun <T, D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.typedProperty(
converter: MetaConverter<T>, converter: MetaConverter<T>,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null, name: String? = null,
read: suspend D.(propertyName: String) -> T?, descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
read: suspend D.(String) -> T?,
write: (suspend D.(String, T) -> Unit)? = null,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, T>>> { ): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, T>>> {
return property(converter, descriptorBuilder, name, read) return if (write == null) {
property(converter, descriptorBuilder, name, read)
} else {
mutableProperty(converter, descriptorBuilder, name, read, write)
}
} }
/** /**
* Mutable property that delegates reading and writing to a device [KMutableProperty1] * Boolean property: read-only or mutable (if [write] is not null).
*/ */
public fun <T, D : Device> CompositeControlComponentSpec<D>.mutableProperty( public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.booleanProperty(
converter: MetaConverter<T>,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null, name: String? = null,
read: suspend D.(propertyName: String) -> T?, descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
write: suspend D.(propertyName: String, value: T) -> Unit, read: suspend D.(String) -> Boolean?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, T>>> { write: (suspend D.(String, Boolean) -> Unit)? = null,
return mutableProperty(converter, descriptorBuilder, name, read, write) ) = typedProperty(MetaConverter.boolean, name, descriptorBuilder, read, write)
}
/** /**
* Register a mutable logical property (without a corresponding physical state) for a device * Int property: read-only or mutable.
*/ */
public fun <T, D : DeviceBase<D>> CompositeControlComponentSpec<D>.logical( public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.intProperty(
converter: MetaConverter<T>,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null, name: String? = null,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, T>>> = descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
mutableProperty( read: suspend D.(String) -> Int?,
converter, write: (suspend D.(String, Int) -> Unit)? = null,
descriptorBuilder, ) = typedProperty(MetaConverter.int, name, descriptorBuilder, read, write)
name,
read = { propertyName -> getProperty(propertyName)?.let(converter::readOrNull) }, /**
write = { propertyName, value -> writeProperty(propertyName, converter.convert(value)) } * Double property: read-only or mutable.
*/
public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.doubleProperty(
name: String? = null,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
read: suspend D.(String) -> Double?,
write: (suspend D.(String, Double) -> Unit)? = null,
) = typedProperty(MetaConverter.double, name, descriptorBuilder, read, write)
/**
* Long property: read-only or mutable.
*/
public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.longProperty(
name: String? = null,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
read: suspend D.(String) -> Long?,
write: (suspend D.(String, Long) -> Unit)? = null,
) = typedProperty(MetaConverter.long, name, descriptorBuilder, read, write)
/**
* Float property: read-only or mutable.
*/
public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.floatProperty(
name: String? = null,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
read: suspend D.(String) -> Float?,
write: (suspend D.(String, Float) -> Unit)? = null,
) = typedProperty(MetaConverter.float, name, descriptorBuilder, read, write)
/**
* Number property: read-only or mutable.
*/
public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.numberProperty(
name: String? = null,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
read: suspend D.(String) -> Number?,
write: (suspend D.(String, Number) -> Unit)? = null,
) = typedProperty(MetaConverter.number, name, descriptorBuilder, read, write)
/**
* String property: read-only or mutable.
*/
public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.stringProperty(
name: String? = null,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
read: suspend D.(String) -> String?,
write: (suspend D.(String, String) -> Unit)? = null,
) = typedProperty(MetaConverter.string, name, descriptorBuilder, read, write)
/**
* Meta property: read-only or mutable.
*/
public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.metaProperty(
name: String? = null,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
read: suspend D.(String) -> Meta?,
write: (suspend D.(String, Meta) -> Unit)? = null,
) = typedProperty(MetaConverter.meta, name, descriptorBuilder, read, write)
/**
* Enum property (read-only or mutable).
* [ignoreCase] controls case sensitivity when reading the enum value from Meta.
* If [write] is null, the property is read-only; otherwise it's read-write.
*/
public inline fun <reified E : Enum<E>, D : ConfigurableCompositeControlComponent<D>>
CompositeControlComponentSpec<D>.enumProperty(
name: String? = null,
ignoreCase: Boolean = false,
noinline descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
noinline read: suspend D.(String) -> E?,
noinline write: (suspend D.(String, E) -> Unit)? = null,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, E>>> =
typedProperty(
converter = createEnumConverter<E>(ignoreCase),
name = name,
descriptorBuilder = descriptorBuilder,
read = read,
write = write
) )
/** /**
* Creates a boolean property for a device. * List property: read-only or mutable.
*/ */
public fun <D : Device> CompositeControlComponentSpec<D>.boolean( public fun <T, D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.listProperty(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {}, listConverter: MetaConverter<List<T>>,
name: String? = null, name: String? = null,
read: suspend D.(propertyName: String) -> Boolean?, descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Boolean>>> = read: suspend D.(String) -> List<T>?,
property(MetaConverter.boolean, descriptorBuilder, name, read) write: (suspend D.(String, List<T>) -> Unit)? = null,
) = typedProperty(listConverter, name, descriptorBuilder, read, write)
/** /**
* Creates a mutable boolean property for a device. * Logical property (no real hardware I/O).
*/ */
public fun <D : Device> CompositeControlComponentSpec<D>.booleanMutable( public fun <T, D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.logicalProperty(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {}, converter: MetaConverter<T>,
name: String? = null, name: String? = null,
read: suspend D.(propertyName: String) -> Boolean?, descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
write: suspend D.(propertyName: String, value: Boolean) -> Unit ) = typedProperty(
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Boolean>>> = converter = converter,
mutableProperty(MetaConverter.boolean, descriptorBuilder, name, read, write) name = name,
descriptorBuilder = descriptorBuilder,
read = { propertyName -> getProperty(propertyName)?.let(converter::readOrNull) },
write = { propertyName, value -> writeProperty(propertyName, converter.convert(value)) }
)
/** /**
* Creates a read-only number property for a device. * Creates an action with optional input/output converters.
*/ */
public fun <D : Device> CompositeControlComponentSpec<D>.number( public fun <I, O, D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.typedAction(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Number?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Number>>> =
property(MetaConverter.number, descriptorBuilder, name, read)
/**
* Creates a mutable number property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.numberMutable(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Number?,
write: suspend D.(propertyName: String, value: Number) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Number>>> =
mutableProperty(MetaConverter.number, descriptorBuilder, name, read, write)
/**
* Creates a read-only double property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.double(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Double?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Double>>> =
property(MetaConverter.double, descriptorBuilder, name, read)
/**
* Creates a mutable double property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.doubleMutable(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Double?,
write: suspend D.(propertyName: String, value: Double) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Double>>> =
mutableProperty(MetaConverter.double, descriptorBuilder, name, read, write)
/**
* Creates a read-only string property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.string(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> String?
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, String>>> =
property(MetaConverter.string, descriptorBuilder, name, read)
/**
* Creates a mutable string property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.stringMutableProperty(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> String?,
write: suspend D.(propertyName: String, value: String) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, String>>> =
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
/**
* Creates a read-only meta property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.meta(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Meta?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Meta>>> =
property(MetaConverter.meta, descriptorBuilder, name, read)
/**
* Creates a mutable meta property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.metaMutable(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Meta?,
write: suspend D.(propertyName: String, value: Meta) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Meta>>> =
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)
/**
* Creates a read-only enum property for a device.
*/
public fun <E : Enum<E>, D : Device> CompositeControlComponentSpec<D>.enum(
enumValues: Array<E>,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> E?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, E>>> {
val converter = createEnumConverter(enumValues)
return property(converter, descriptorBuilder, name, read)
}
/**
* Creates a mutable enum property for a device.
*/
public fun <E : Enum<E>, D : Device> CompositeControlComponentSpec<D>.enumMutable(
enumValues: Array<E>,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> E?,
write: suspend D.(propertyName: String, value: E) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, E>>> {
val converter = createEnumConverter(enumValues)
return mutableProperty(converter, descriptorBuilder, name, read, write)
}
/**
* Creates a read-only float property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.float(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Float?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Float>>> =
property(MetaConverter.float, descriptorBuilder, name, read)
/**
* Creates a mutable float property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.floatMutable(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Float?,
write: suspend D.(propertyName: String, value: Float) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Float>>> =
mutableProperty(MetaConverter.float, descriptorBuilder, name, read, write)
/**
* Creates a read-only long property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.long(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Long?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Long>>> =
property(MetaConverter.long, descriptorBuilder, name, read)
/**
* Creates a mutable long property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.longMutable(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Long?,
write: suspend D.(propertyName: String, value: Long) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Long>>> =
mutableProperty(MetaConverter.long, descriptorBuilder, name, read, write)
/**
* Creates a read-only int property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.int(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Int?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Int>>> =
property(MetaConverter.int, descriptorBuilder, name, read)
/**
* Creates a mutable int property for a device.
*/
public fun <D : Device> CompositeControlComponentSpec<D>.intMutable(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Int?,
write: suspend D.(propertyName: String, value: Int) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Int>>> =
mutableProperty(MetaConverter.int, descriptorBuilder, name, read, write)
/**
* Creates a read-only list property for a device.
*/
public fun <T, D : Device> CompositeControlComponentSpec<D>.list(
converter: MetaConverter<List<T>>,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> List<T>?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, List<T>>>> =
property(converter, descriptorBuilder, name, read)
/**
* Creates a mutable list property for a device.
*/
public fun <T, D : Device> CompositeControlComponentSpec<D>.listMutable(
converter: MetaConverter<List<T>>,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> List<T>?,
write: suspend D.(propertyName: String, value: List<T>) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, List<T>>>> =
mutableProperty(converter, descriptorBuilder, name, read, write)
public fun <I, O, D : Device> CompositeControlComponentSpec<D>.asyncActionProperty(
inputConverter: MetaConverter<I>, inputConverter: MetaConverter<I>,
outputConverter: MetaConverter<O>, outputConverter: MetaConverter<O>,
name: String? = null,
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {}, descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
name: String? = null, execute: suspend D.(I) -> O,
execute: suspend D.(I) -> Deferred<O>,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, I, O>>> = ): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, I, O>>> =
action(inputConverter, outputConverter, descriptorBuilder, name) { input -> action(
execute(input).await() inputConverter = inputConverter,
} outputConverter = outputConverter,
descriptorBuilder = descriptorBuilder,
public fun <T, D : Device> CompositeControlComponentSpec<D>.metaProperty( name = name,
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {}, execute = execute
name: String? = null, )
read: suspend D.(propertyName: String) -> Meta?,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Meta>>> =
property(MetaConverter.meta, descriptorBuilder, name, read)
public fun <T, D : Device> CompositeControlComponentSpec<D>.mutableMetaProperty(
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Meta?,
write: suspend D.(propertyName: String, value: Meta) -> Unit
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Meta>>> =
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)
/** /**
* An action that takes no parameters and returns no values * Action with no parameters and no return values.
*/ */
public fun <T, D : Device> CompositeControlComponentSpec<D>.unitAction( public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.unitAction(
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
name: String? = null, name: String? = null,
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
execute: suspend D.() -> Unit, execute: suspend D.() -> Unit,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, Unit, Unit>>> = ): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, Unit, Unit>>> =
action( typedAction(
MetaConverter.unit, inputConverter = MetaConverter.unit,
MetaConverter.unit, outputConverter = MetaConverter.unit,
descriptorBuilder, name = name,
name descriptorBuilder = descriptorBuilder,
) { ) {
execute() execute()
} }
public fun <I, O, D : Device> CompositeControlComponentSpec<D>.asyncAction( /**
* Action with async result. The result is awaited.
*/
public fun <I, O, D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.asyncAction(
inputConverter: MetaConverter<I>, inputConverter: MetaConverter<I>,
outputConverter: MetaConverter<O>, outputConverter: MetaConverter<O>,
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
name: String? = null, name: String? = null,
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
execute: suspend D.(I) -> Deferred<O>, execute: suspend D.(I) -> Deferred<O>,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, I, O>>> = ): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, I, O>>> =
action( typedAction(inputConverter, outputConverter, name, descriptorBuilder) { input ->
inputConverter, execute(input).await()
outputConverter,
descriptorBuilder,
name
) {
execute(it).await()
} }
/** /**
* An action that takes [Meta] and returns [Meta]. No conversions are done * Action that takes and returns [Meta].
*/ */
public fun <T, D : Device> CompositeControlComponentSpec<D>.metaAction( public fun <D : ConfigurableCompositeControlComponent<D>> CompositeControlComponentSpec<D>.metaAction(
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
name: String? = null, name: String? = null,
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
execute: suspend D.(Meta) -> Meta, execute: suspend D.(Meta) -> Meta,
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, Meta, Meta>>> = ): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
action( typedAction(MetaConverter.meta, MetaConverter.meta, name, descriptorBuilder, execute)
MetaConverter.meta,
MetaConverter.meta,
descriptorBuilder,
name
) {
execute(it)
}
/** /**
* Throw an exception if device does not have all properties and actions defined by this specification * Validates that [device] has all properties and actions defined by this spec.
*/ */
public fun CompositeControlComponentSpec<*>.validate(device: Device) { public fun CompositeControlComponentSpec<*>.validateSpec(device: Device) {
properties.map { it.value.descriptor }.forEach { specProperty -> properties.values.forEach { propSpec ->
check(specProperty in device.propertyDescriptors) { "Property ${specProperty.name} not registered in ${device.id}" } check(propSpec.descriptor in device.propertyDescriptors) {
"Property ${propSpec.descriptor.name} not registered in ${device.id}"
}
}
actions.values.forEach { actSpec ->
check(actSpec.descriptor in device.actionDescriptors) {
"Action ${actSpec.descriptor.name} not registered in ${device.id}"
} }
actions.map { it.value.descriptor }.forEach { specAction ->
check(specAction in device.actionDescriptors) { "Action ${specAction.name} not registered in ${device.id}" }
} }
} }

View File

@ -18,98 +18,92 @@ class CompositeControlTest {
// ---------------------- Device Specifications ---------------------------------- // ---------------------- Device Specifications ----------------------------------
public object StepperMotorSpec : CompositeControlComponentSpec<StepperMotorDevice>() { public object StepperMotorSpec : CompositeControlComponentSpec<StepperMotorDevice>() {
public val position by intMutable( public val position by intProperty(
name = "position", name = "position",
read = { getPosition() }, read = { getPosition() },
write = { _, value -> setPosition(value) } write = { _, value -> setPosition(value) }
) )
public val maxPosition by int( public val maxPosition by intProperty(
name = "maxPosition", name = "maxPosition",
read = { maxPosition } read = { maxPosition }
) )
} }
public object ValveSpec : CompositeControlComponentSpec<ValveDevice>() { public object ValveSpec : CompositeControlComponentSpec<ValveDevice>() {
public val state by booleanMutable( public val state by booleanProperty(
read = { getState() }, read = { getState() },
write = { _, value -> setState(value) } write = { _, value -> setState(value) }
) )
} }
public object PressureChamberSpec : CompositeControlComponentSpec<PressureChamberDevice>() { public object PressureChamberSpec : CompositeControlComponentSpec<PressureChamberDevice>() {
public val pressure by doubleMutable( public val pressure by doubleProperty(
read = { getPressure() }, read = { getPressure() },
write = { _, value -> setPressure(value) } write = { _, value -> setPressure(value) }
) )
} }
public object SyringePumpSpec : CompositeControlComponentSpec<SyringePumpDevice>() { public object SyringePumpSpec : CompositeControlComponentSpec<SyringePumpDevice>() {
public val volume by doubleMutable( public val volume by doubleProperty(
read = { getVolume() }, read = { getVolume() },
write = { _, value -> setVolume(value) } write = { _, value -> setVolume(value) }
) )
} }
public object ReagentSensorSpec : CompositeControlComponentSpec<ReagentSensorDevice>() { public object ReagentSensorSpec : CompositeControlComponentSpec<ReagentSensorDevice>() {
public val isPresent by boolean( public val isPresent by booleanProperty(
read = { checkReagent() } read = { checkReagent() }
) )
} }
public object NeedleSpec : CompositeControlComponentSpec<NeedleDevice>() { public object NeedleSpec : CompositeControlComponentSpec<NeedleDevice>() {
public val mode by enumMutable( public val mode by enumProperty<NeedleDevice.Mode, NeedleDevice>(
enumValues = NeedleDevice.Mode.entries.toTypedArray(),
read = { getMode() }, read = { getMode() },
write = { _, value -> setMode(value) } write = { _, value -> setMode(value) }
) )
public val position by doubleMutable( public val position by doubleProperty(
read = { getPosition() }, read = { getPosition() },
write = { _, value -> setPosition(value) } write = { _, value -> setPosition(value) }
) )
} }
public object ShakerSpec : CompositeControlComponentSpec<ShakerDevice>() { public object ShakerSpec : CompositeControlComponentSpec<ShakerDevice>() {
public val verticalMotor by childSpec<StepperMotorSpec, StepperMotorDevice>() public val verticalMotor by childSpec<StepperMotorSpec, StepperMotorDevice>(StepperMotorSpec)
public val horizontalMotor by childSpec<StepperMotorSpec, StepperMotorDevice>() public val horizontalMotor by childSpec<StepperMotorSpec, StepperMotorDevice>(StepperMotorSpec)
} }
public object TransportationSystemSpec : CompositeControlComponentSpec<TransportationSystem>() { public object TransportationSystemSpec : CompositeControlComponentSpec<TransportationSystem>() {
public val slideMotor by childSpec<StepperMotorSpec, StepperMotorDevice>() public val slideMotor by childSpec<StepperMotorSpec, StepperMotorDevice>(StepperMotorSpec)
public val pushMotor by childSpec<StepperMotorSpec, StepperMotorDevice>(StepperMotorSpec)
public val pushMotor by childSpec<StepperMotorSpec, StepperMotorDevice>() public val receiveMotor by childSpec<StepperMotorSpec, StepperMotorDevice>(StepperMotorSpec)
public val receiveMotor by childSpec<StepperMotorSpec, StepperMotorDevice>()
} }
public object AnalyzerSpec : CompositeControlComponentSpec<AnalyzerDevice>() { public object AnalyzerSpec : CompositeControlComponentSpec<AnalyzerDevice>() {
public val transportationSystem by childSpec<TransportationSystemSpec, TransportationSystemSpec>() public val transportationSystem by childSpec<TransportationSystemSpec, TransportationSystem>(TransportationSystemSpec)
public val shakerDevice by childSpec<ShakerSpec, ShakerDevice>() public val shakerDevice by childSpec<ShakerSpec, ShakerDevice>(ShakerSpec)
public val needleDevice by childSpec<NeedleSpec, NeedleDevice>() public val needleDevice by childSpec<NeedleSpec, NeedleDevice>(NeedleSpec)
public val valveV20 by childSpec<ValveSpec, ValveDevice>(ValveSpec)
public val valveV17 by childSpec<ValveSpec, ValveDevice>(ValveSpec)
public val valveV18 by childSpec<ValveSpec, ValveDevice>(ValveSpec)
public val valveV35 by childSpec<ValveSpec, ValveDevice>(ValveSpec)
public val valveV20 by childSpec<ValveSpec, ValveDevice>() public val pressureChamberHigh by childSpec<PressureChamberSpec, PressureChamberDevice>(PressureChamberSpec)
public val valveV17 by childSpec<ValveSpec, ValveDevice>() public val pressureChamberLow by childSpec<PressureChamberSpec, PressureChamberDevice>(PressureChamberSpec)
public val valveV18 by childSpec<ValveSpec, ValveDevice>()
public val valveV35 by childSpec<ValveSpec, ValveDevice>()
public val syringePumpMA100 by childSpec<SyringePumpSpec, SyringePumpDevice>(SyringePumpSpec)
public val syringePumpMA25 by childSpec<SyringePumpSpec, SyringePumpDevice>(SyringePumpSpec)
public val pressureChamberHigh by childSpec<PressureChamberSpec, PressureChamberDevice>() public val reagentSensor1 by childSpec<ReagentSensorSpec, ReagentSensorDevice>(ReagentSensorSpec)
public val pressureChamberLow by childSpec<PressureChamberSpec, PressureChamberDevice>() public val reagentSensor2 by childSpec<ReagentSensorSpec, ReagentSensorDevice>(ReagentSensorSpec)
public val reagentSensor3 by childSpec<ReagentSensorSpec, ReagentSensorDevice>(ReagentSensorSpec)
public val syringePumpMA100 by childSpec<SyringePumpSpec, SyringePumpDevice>()
public val syringePumpMA25 by childSpec<SyringePumpSpec, SyringePumpDevice>()
public val reagentSensor1 by childSpec<ReagentSensorSpec, ReagentSensorDevice>()
public val reagentSensor2 by childSpec<ReagentSensorSpec, ReagentSensorDevice>()
public val reagentSensor3 by childSpec<ReagentSensorSpec, ReagentSensorDevice>()
} }
// ---------------------- Device Implementations ---------------------------------- // ---------------------- Device Implementations ----------------------------------
// Implementation of Stepper Motor Device
public class StepperMotorDevice( public class StepperMotorDevice(
context: Context, context: Context,
meta: Meta = Meta.EMPTY meta: Meta = Meta.EMPTY
@ -774,6 +768,9 @@ class CompositeControlTest {
val context = createTestContext() val context = createTestContext()
val shaker = ShakerDevice(context) val shaker = ShakerDevice(context)
shaker.initChildren()
shaker.start()// Start the device
// Access properties to initialize motors and test shaking // Access properties to initialize motors and test shaking
val verticalMotor = shaker.verticalMotor val verticalMotor = shaker.verticalMotor
val horizontalMotor = shaker.horizontalMotor val horizontalMotor = shaker.horizontalMotor
@ -791,19 +788,23 @@ class CompositeControlTest {
val context = createTestContext() val context = createTestContext()
val transportationSystem = TransportationSystem(context) val transportationSystem = TransportationSystem(context)
transportationSystem.initChildren()
transportationSystem.start()// Start the device
// Access properties to initialize motors and test existence // Access properties to initialize motors and test existence
assertNotNull(transportationSystem.slideMotor, "slideMotor should exist") assertNotNull(transportationSystem.slideMotor, "slideMotor should exist")
assertNotNull(transportationSystem.pushMotor, "pushMotor should exist") assertNotNull(transportationSystem.pushMotor, "pushMotor should exist")
assertNotNull(transportationSystem.receiveMotor, "receiveMotor should exist") assertNotNull(transportationSystem.receiveMotor, "receiveMotor should exist")
} }
@Test @Test
fun `test AnalyzerDevice device access`() = runTest{ fun `test AnalyzerDevice device access`() = runTest {
val context = createTestContext() val context = createTestContext()
val analyzer = AnalyzerDevice(context) val analyzer = AnalyzerDevice(context)
analyzer.initChildren()
analyzer.start()// Start the device
// Access properties to initialize child devices and test existence // Access properties to initialize child devices
assertNotNull(analyzer.transportationSystem, "Transportation system should exist") assertNotNull(analyzer.transportationSystem, "Transportation system should exist")
assertNotNull(analyzer.shakerDevice, "Shaker device should exist") assertNotNull(analyzer.shakerDevice, "Shaker device should exist")
assertNotNull(analyzer.needleDevice, "Needle device should exist") assertNotNull(analyzer.needleDevice, "Needle device should exist")