Added some examples of new API usage. Not all tests are passed.

This commit is contained in:
Максим Колпаков 2024-12-19 12:11:51 +03:00
parent e9e2c7b8d8
commit 76fa751e25

View File

@ -0,0 +1,822 @@
package space.kscience.controls.spec
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.names.parseAsName
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class CompositeControlTest {
// ---------------------- Device Specifications ----------------------------------
public object StepperMotorSpec : CompositeControlComponentSpec<StepperMotorDevice>() {
public val position by intMutable(
name = "position",
read = { getPosition() },
write = { _, value -> setPosition(value) }
)
public val maxPosition by int(
name = "maxPosition",
read = { maxPosition }
)
}
public object ValveSpec : CompositeControlComponentSpec<ValveDevice>() {
public val state by booleanMutable(
read = { getState() },
write = { _, value -> setState(value) }
)
}
public object PressureChamberSpec : CompositeControlComponentSpec<PressureChamberDevice>() {
public val pressure by doubleMutable(
read = { getPressure() },
write = { _, value -> setPressure(value) }
)
}
public object SyringePumpSpec : CompositeControlComponentSpec<SyringePumpDevice>() {
public val volume by doubleMutable(
read = { getVolume() },
write = { _, value -> setVolume(value) }
)
}
public object ReagentSensorSpec : CompositeControlComponentSpec<ReagentSensorDevice>() {
public val isPresent by boolean(
read = { checkReagent() }
)
}
public object NeedleSpec : CompositeControlComponentSpec<NeedleDevice>() {
public val mode by enumMutable(
enumValues = NeedleDevice.Mode.entries.toTypedArray(),
read = { getMode() },
write = { _, value -> setMode(value) }
)
public val position by doubleMutable(
read = { getPosition() },
write = { _, value -> setPosition(value) }
)
}
public object ShakerSpec : CompositeControlComponentSpec<ShakerDevice>() {
public val verticalMotor by childSpec<StepperMotorSpec, StepperMotorDevice>()
public val horizontalMotor by childSpec<StepperMotorSpec, StepperMotorDevice>()
}
public object TransportationSystemSpec : CompositeControlComponentSpec<TransportationSystem>() {
public val slideMotor by childSpec<StepperMotorSpec, StepperMotorDevice>()
public val pushMotor by childSpec<StepperMotorSpec, StepperMotorDevice>()
public val receiveMotor by childSpec<StepperMotorSpec, StepperMotorDevice>()
}
public object AnalyzerSpec : CompositeControlComponentSpec<AnalyzerDevice>() {
public val transportationSystem by childSpec<TransportationSystemSpec, TransportationSystemSpec>()
public val shakerDevice by childSpec<ShakerSpec, ShakerDevice>()
public val needleDevice by childSpec<NeedleSpec, NeedleDevice>()
public val valveV20 by childSpec<ValveSpec, ValveDevice>()
public val valveV17 by childSpec<ValveSpec, ValveDevice>()
public val valveV18 by childSpec<ValveSpec, ValveDevice>()
public val valveV35 by childSpec<ValveSpec, ValveDevice>()
public val pressureChamberHigh by childSpec<PressureChamberSpec, PressureChamberDevice>()
public val pressureChamberLow by childSpec<PressureChamberSpec, PressureChamberDevice>()
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 ----------------------------------
// Implementation of Stepper Motor Device
public class StepperMotorDevice(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<StepperMotorDevice>(StepperMotorSpec, context, meta) {
private var _position: Int = 0
public val maxPosition: Int = meta["maxPosition".parseAsName()].int ?: 100
/**
* Get current position of the stepper motor.
* @return Current position as Int
*/
public suspend fun getPosition(): Int = _position
/**
* Set position of the stepper motor, if position is valid the move will occur.
* @param value target position as Int
*/
public suspend fun setPosition(value: Int) {
if (value in 0..maxPosition) {
_position = value
println("StepperMotorDevice: Moving to position $_position")
delay(100)
} else {
println("StepperMotorDevice: Invalid position $value (max: $maxPosition)")
}
}
}
// Implementation of Valve Device
public class ValveDevice(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<ValveDevice>(ValveSpec, context, meta) {
private var _state: Boolean = false
/**
* Get current state of the valve
* @return true if valve is open, false if closed
*/
public suspend fun getState(): Boolean = _state
/**
* Set the current state of the valve and print the change.
* @param value true if valve should be open, false if should be closed
*/
public suspend fun setState(value: Boolean) {
_state = value
val stateStr = if (_state) "open" else "closed"
println("ValveDevice: Valve is now $stateStr")
delay(50)
}
/**
* Simulates clicking the valve.
*/
public suspend fun click() {
println("ValveDevice: Clicking valve...")
setState(true)
delay(50)
setState(false)
println("ValveDevice: Valve click completed")
}
}
// Implementation of Pressure Chamber Device
public class PressureChamberDevice(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<PressureChamberDevice>(PressureChamberSpec, context, meta) {
private var _pressure: Double = 0.0
/**
* Get the current pressure in the chamber.
* @return current pressure as Double
*/
public suspend fun getPressure(): Double = _pressure
/**
* Set the pressure in the chamber.
* @param value target pressure as Double
*/
public suspend fun setPressure(value: Double) {
_pressure = value
println("PressureChamberDevice: Pressure is now $_pressure")
delay(50)
}
}
// Implementation of Syringe Pump Device
public class SyringePumpDevice(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<SyringePumpDevice>(SyringePumpSpec, context, meta) {
private var _volume: Double = 0.0
public val maxVolume: Double = meta["maxVolume".parseAsName()].double ?: 5.0
/**
* Get current volume in the syringe
* @return volume as Double
*/
public suspend fun getVolume(): Double = _volume
/**
* Set the current volume in the syringe.
* @param value the target volume as Double
*/
public suspend fun setVolume(value: Double) {
if (value in 0.0..maxVolume) {
_volume = value
println("SyringePumpDevice: Volume is now $_volume ml")
delay(100)
} else {
println("SyringePumpDevice: Invalid volume $value (max: $maxVolume)")
}
}
}
// Implementation of Reagent Sensor Device
public class ReagentSensorDevice(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<ReagentSensorDevice>(ReagentSensorSpec, context, meta) {
/**
* Checks for reagent presence.
* @return true if reagent is present.
*/
public suspend fun checkReagent(): Boolean {
println("ReagentSensorDevice: Checking for reagent presence...")
delay(100) // Simulate detection time
val isPresent = true // Assume reagent is present
println("ReagentSensorDevice: Reagent is ${if (isPresent) "present" else "not present"}")
return isPresent
}
}
// Implementation of Needle Device
public class NeedleDevice(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<NeedleDevice>(NeedleSpec, context, meta) {
public enum class Mode { SAMPLING, WASHING }
private var _mode: Mode = Mode.WASHING
private var _position: Double = 0.0
/**
* Get the current mode of needle.
* @return current mode of the needle.
*/
public suspend fun getMode(): Mode = _mode
/**
* Set the mode of the needle
* @param value the target mode
*/
public suspend fun setMode(value: Mode) {
_mode = value
println("NeedleDevice: Mode is now $_mode")
delay(50)
}
/**
* Get current position of the needle
* @return current position as Double
*/
public suspend fun getPosition(): Double = _position
/**
* Set the needle position
* @param value target position as Double
*/
public suspend fun setPosition(value: Double) {
if (value in 0.0..100.0) {
_position = value
println("NeedleDevice: Moved to position $_position mm")
delay(100)
} else {
println("NeedleDevice: Invalid position $value mm")
}
}
/**
* Executes washing process for given duration
* @param duration time for washing in seconds
*/
public suspend fun performWashing(duration: Int) {
println("NeedleDevice: Washing in progress for $duration seconds")
delay(duration * 1000L) // Simulate washing (1 second = 1000 ms)
}
/**
* Execute sampling procedure
*/
public suspend fun performSampling() {
println("NeedleDevice: Performing sample intake at position $_position mm")
delay(500) // Simulate sampling time
}
}
// Implementation of Shaker Device
public class ShakerDevice(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<ShakerDevice>(ShakerSpec, context, meta) {
/**
* Get vertical stepper motor
*/
public val verticalMotor by childDevice<StepperMotorDevice>()
/**
* Get horizontal stepper motor
*/
public val horizontalMotor by childDevice<StepperMotorDevice>()
/**
* Shakes the device for given cycles.
* @param cycles amount of cycles for shaking
*/
public suspend fun shake(cycles: Int) {
println("ShakerDevice: Shaking started, cycles: $cycles")
repeat(cycles) {
verticalMotor.setPosition(3)
verticalMotor.setPosition(1)
println("ShakerDevice: cycle ${it+1} done")
}
println("ShakerDevice: Shaking completed")
}
}
// Implementation of Transportation System
public class TransportationSystem(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<TransportationSystem>(TransportationSystemSpec, context, meta) {
/**
* Get slide stepper motor
*/
public val slideMotor by childDevice<StepperMotorDevice>()
/**
* Get push stepper motor
*/
public val pushMotor by childDevice<StepperMotorDevice>()
/**
* Get receive stepper motor
*/
public val receiveMotor by childDevice<StepperMotorDevice>()
}
// Implementation of Analyzer Device
public class AnalyzerDevice(
context: Context,
meta: Meta = Meta.EMPTY
) : ConfigurableCompositeControlComponent<AnalyzerDevice>(AnalyzerSpec, context, meta) {
/**
* Get transportation system
*/
public val transportationSystem by childDevice<TransportationSystem>()
/**
* Get shaker device
*/
public val shakerDevice by childDevice<ShakerDevice>()
/**
* Get needle device
*/
public val needleDevice by childDevice<NeedleDevice>()
/**
* Get valve V20
*/
public val valveV20 by childDevice<ValveDevice>()
/**
* Get valve V17
*/
public val valveV17 by childDevice<ValveDevice>()
/**
* Get valve V18
*/
public val valveV18 by childDevice<ValveDevice>()
/**
* Get valve V35
*/
public val valveV35 by childDevice<ValveDevice>()
/**
* Get high pressure chamber
*/
public val pressureChamberHigh by childDevice<PressureChamberDevice>()
/**
* Get low pressure chamber
*/
public val pressureChamberLow by childDevice<PressureChamberDevice>()
/**
* Get syringe pump MA100
*/
public val syringePumpMA100 by childDevice<SyringePumpDevice>()
/**
* Get syringe pump MA25
*/
public val syringePumpMA25 by childDevice<SyringePumpDevice>()
/**
* Get reagent sensor 1
*/
public val reagentSensor1 by childDevice<ReagentSensorDevice>()
/**
* Get reagent sensor 2
*/
public val reagentSensor2 by childDevice<ReagentSensorDevice>()
/**
* Get reagent sensor 3
*/
public val reagentSensor3 by childDevice<ReagentSensorDevice>()
/**
* Simulates a process of taking sample from tubes.
*/
public suspend fun processSample() {
println("The beginning of the sampling process")
// Step 1: Open valve V20 and start taking a sample with a syringe pump MA 100 mcl
valveV20.setState(true)
syringePumpMA100.setVolume(0.1)
delay(500) // Simulating waiting time for liquid collection
valveV20.setState(false)
// Step 2: Open valve V17 and start delivering lysis buffer with syringe pump MA 2.5 ml
valveV17.setState(true)
syringePumpMA25.setVolume(2.5)
delay(500) // Simulate lysis buffer delivery time
valveV17.setState(false)
// Step 3: Cleaning system
syringePumpMA100.setVolume(0.0)
syringePumpMA25.setVolume(0.0)
println("The sampling process is completed")
}
/**
* Simulates the analyzer calibration procedure.
*/
public suspend fun calibrate() {
println("The beginning of calibration...")
// Step 1: Calibrate positions of all motors
val motors = listOf(
transportationSystem.slideMotor,
transportationSystem.pushMotor,
transportationSystem.receiveMotor,
shakerDevice.verticalMotor,
shakerDevice.horizontalMotor,
)
for (motor in motors) {
for (position in 0..motor.maxPosition) {
motor.setPosition(position)
}
motor.setPosition(0)
}
// Step 2: Click all valves and set them to zero position
val valves = listOf(valveV20, valveV17, valveV18, valveV35)
for (valve in valves) {
valve.click()
valve.setState(false)
}
// Step 3: Pump up pressure in high pressure chamber
pressureChamberHigh.setPressure(2.0)
// Step 4: Pump out pressure from low pressure chamber
pressureChamberLow.setPressure(-1.0)
// Step 5: Fill the hydraulic system
// 5.1 Check if reagents are present
val sensors = listOf(reagentSensor1, reagentSensor2, reagentSensor3)
for (sensor in sensors) {
sensor.checkReagent()
}
// 5.2 Perform 5 times full pump movement with all syringe pumps
val pumps = listOf(syringePumpMA100, syringePumpMA25)
for (pump in pumps) {
repeat(5) {
pump.setVolume(pump.maxVolume)
pump.setVolume(0.0)
}
}
// 5.3 Wash needle at its washing position
needleDevice.setPosition(0.0)
needleDevice.setMode(NeedleDevice.Mode.WASHING)
needleDevice.performWashing(5)
println("Calibration is completed")
}
/**
* Execute recipe 1 - sample tube deliver
*/
public suspend fun executeRecipe1() {
println("Executing recipe 1")
// Step 1: Move a slide to the next position
val currentSlidePosition = transportationSystem.slideMotor.getPosition()
transportationSystem.slideMotor.setPosition(currentSlidePosition + 1)
println("Moved a slide to position ${currentSlidePosition+1}")
// Step 2: Capture a tube for mixing
println("Capturing tube for mixing")
// 2.1 - 2.10: Control over a shaker and motors
shakerDevice.verticalMotor.setPosition(1)
shakerDevice.horizontalMotor.setPosition(1)
println("Shaker: vertical - 1, horizontal - 1")
shakerDevice.horizontalMotor.setPosition(2)
println("Shaker: horizontal - 2")
shakerDevice.verticalMotor.setPosition(2)
println("Shaker: vertical - 2")
// Shake
shakerDevice.shake(5)
println("Shaker: movement done")
// Step 3: Sampling and measurement
executeSampling()
needleDevice.setPosition(0.0)
println("Needle moved to its initial position")
}
/**
* Execute recipe 2 - Automatic Measurement
*/
public suspend fun executeRecipe2() {
println("Executing Recipe 2 - Automatic Measurement")
transportationSystem.receiveMotor.setPosition(transportationSystem.receiveMotor.getPosition() + 1)
println("Pusher moved to position ${transportationSystem.receiveMotor.getPosition() + 1}")
//Check for a tray, if missing move again
if (!checkTrayInPushSystem()) {
println("Tray missing. Trying to move again")
transportationSystem.receiveMotor.setPosition(transportationSystem.receiveMotor.getPosition() + 1)
} else {
executeSampling()
}
// If last position, reset the plate
if (transportationSystem.receiveMotor.getPosition() >= transportationSystem.receiveMotor.maxPosition) {
println("Plate is complete. Resetting pusher to initial position")
transportationSystem.receiveMotor.setPosition(0)
}
println("Recipe 2 execution finished")
needleDevice.setPosition(0.0)
println("Needle moved to its initial position")
}
/**
* Execute recipe 3 - Single Measurement
*/
public suspend fun executeRecipe3() {
println("Executing Recipe 3 - Single measurement")
executeSampling()
println("Recipe 3 completed")
needleDevice.setPosition(0.0)
println("Needle moved to its initial position")
}
/**
* Simulates tray presence check
*/
private suspend fun checkTrayInPushSystem(): Boolean {
println("Checking for a tray in a pushing system")
delay(200)
return true // Simulate tray presence
}
/**
* Function to execute sampling process with the needle
*/
private suspend fun executeSampling() {
needleDevice.setMode(NeedleDevice.Mode.SAMPLING)
needleDevice.performSampling()
needleDevice.setMode(NeedleDevice.Mode.WASHING)
needleDevice.performWashing(2)
}
}
private fun createTestContext() = Context("test")
@Test
fun `test StepperMotorDevice position setting`() = runTest {
val context = createTestContext()
val motor = StepperMotorDevice(context, Meta { "maxPosition" put 500 })
motor.setPosition(200)
assertEquals(200, motor.getPosition(), "Position should be set correctly")
motor.setPosition(0)
assertEquals(0, motor.getPosition(), "Position should be reset to 0")
motor.setPosition(500)
assertEquals(500, motor.getPosition(), "Position should be set to max value")
}
@Test
fun `test StepperMotorDevice invalid position`() = runTest {
val context = createTestContext()
val motor = StepperMotorDevice(context, Meta { "maxPosition" put 100 })
motor.setPosition(200) //Should be outside the range, so not changed
assertEquals(0, motor.getPosition(), "Position should not be set for invalid value")
}
@Test
fun `test ValveDevice state toggling`() = runTest {
val context = createTestContext()
val valve = ValveDevice(context)
assertFalse(valve.getState(), "Initial state should be closed")
valve.setState(true)
assertTrue(valve.getState(), "State should be set to open")
valve.setState(false)
assertFalse(valve.getState(), "State should be set to closed")
}
@Test
fun `test ValveDevice click operation`() = runTest {
val context = createTestContext()
val valve = ValveDevice(context)
assertFalse(valve.getState(), "Initial state should be closed")
valve.click()
assertFalse(valve.getState(), "Valve should be closed after click")
}
@Test
fun `test PressureChamberDevice pressure setting`() = runTest {
val context = createTestContext()
val chamber = PressureChamberDevice(context)
chamber.setPressure(1.5)
assertEquals(1.5, chamber.getPressure(), "Pressure should be set correctly")
chamber.setPressure(0.0)
assertEquals(0.0, chamber.getPressure(), "Pressure should be set to 0")
chamber.setPressure(-1.0)
assertEquals(-1.0, chamber.getPressure(), "Pressure should be set to negative value")
}
@Test
fun `test SyringePumpDevice volume setting`() = runTest {
val context = createTestContext()
val pump = SyringePumpDevice(context, Meta { "maxVolume" put 10.0 })
pump.setVolume(3.5)
assertEquals(3.5, pump.getVolume(), "Volume should be set correctly")
pump.setVolume(0.0)
assertEquals(0.0, pump.getVolume(), "Volume should be reset to 0")
pump.setVolume(10.0)
assertEquals(10.0, pump.getVolume(), "Volume should be set to max value")
}
@Test
fun `test SyringePumpDevice invalid volume`() = runTest {
val context = createTestContext()
val pump = SyringePumpDevice(context, Meta { "maxVolume" put 5.0 })
pump.setVolume(10.0)
assertEquals(0.0, pump.getVolume(), "Pump volume should not be set for invalid value")
}
@Test
fun `test ReagentSensorDevice checkReagent returns true`() = runTest {
val context = createTestContext()
val sensor = ReagentSensorDevice(context)
assertTrue(sensor.checkReagent(), "Reagent sensor should report presence by default")
}
@Test
fun `test NeedleDevice position and mode setting`() = runTest {
val context = createTestContext()
val needle = NeedleDevice(context)
//Test setting mode
needle.setMode(NeedleDevice.Mode.SAMPLING)
assertEquals(NeedleDevice.Mode.SAMPLING, needle.getMode(), "Mode should be set to SAMPLING")
needle.setMode(NeedleDevice.Mode.WASHING)
assertEquals(NeedleDevice.Mode.WASHING, needle.getMode(), "Mode should be set to WASHING")
//Test setting position
needle.setPosition(50.0)
assertEquals(50.0, needle.getPosition(), "Position should be set correctly")
needle.setPosition(0.0)
assertEquals(0.0, needle.getPosition(), "Position should be set to 0")
needle.setPosition(100.0)
assertEquals(100.0, needle.getPosition(), "Position should be set to max")
}
@Test
fun `test NeedleDevice invalid position`() = runTest {
val context = createTestContext()
val needle = NeedleDevice(context)
needle.setPosition(200.0)
assertEquals(0.0, needle.getPosition(), "Needle position should not be set for invalid value")
}
@Test
fun `test ShakerDevice shaking`() = runTest {
val context = createTestContext()
val shaker = ShakerDevice(context)
// Access properties to initialize motors and test shaking
val verticalMotor = shaker.verticalMotor
val horizontalMotor = shaker.horizontalMotor
shaker.shake(2)
val verticalMotorPosition = verticalMotor.getPosition()
val horizontalMotorPosition = horizontalMotor.getPosition()
assertEquals(2,verticalMotorPosition, "Vertical motor position should be set to 2 after shaking")
assertEquals(1,horizontalMotorPosition, "Horizontal motor position should be set to 1 after shaking")
}
@Test
fun `test TransportationSystem motors existence`() = runTest {
val context = createTestContext()
val transportationSystem = TransportationSystem(context)
// Access properties to initialize motors and test existence
assertNotNull(transportationSystem.slideMotor, "slideMotor should exist")
assertNotNull(transportationSystem.pushMotor, "pushMotor should exist")
assertNotNull(transportationSystem.receiveMotor, "receiveMotor should exist")
}
@Test
fun `test AnalyzerDevice device access`() = runTest{
val context = createTestContext()
val analyzer = AnalyzerDevice(context)
// Access properties to initialize child devices and test existence
assertNotNull(analyzer.transportationSystem, "Transportation system should exist")
assertNotNull(analyzer.shakerDevice, "Shaker device should exist")
assertNotNull(analyzer.needleDevice, "Needle device should exist")
assertNotNull(analyzer.valveV20, "Valve V20 should exist")
assertNotNull(analyzer.valveV17, "Valve V17 should exist")
assertNotNull(analyzer.valveV18, "Valve V18 should exist")
assertNotNull(analyzer.valveV35, "Valve V35 should exist")
assertNotNull(analyzer.pressureChamberHigh, "High pressure chamber should exist")
assertNotNull(analyzer.pressureChamberLow, "Low pressure chamber should exist")
assertNotNull(analyzer.syringePumpMA100, "Syringe pump MA100 should exist")
assertNotNull(analyzer.syringePumpMA25, "Syringe pump MA25 should exist")
assertNotNull(analyzer.reagentSensor1, "Reagent sensor 1 should exist")
assertNotNull(analyzer.reagentSensor2, "Reagent sensor 2 should exist")
assertNotNull(analyzer.reagentSensor3, "Reagent sensor 3 should exist")
}
}