From 9482ba3d97c33aaf9f5fcfd8f220c5273bd95ff2 Mon Sep 17 00:00:00 2001 From: Anthony Berg Date: Thu, 2 May 2024 18:01:35 +0100 Subject: [PATCH] feat(connector): add database logging for simulator test --- .../src/desktopMain/kotlin/InterfaceState.kt | 1 + .../src/desktopMain/kotlin/di/CommonModule.kt | 10 ++ .../desktopMain/kotlin/tab/test/TestRun.kt | 90 ++++++------ .../kotlin/tab/test/TestScreenModel.kt | 130 ++++++++++++++++++ .../anthonyberg/connector/shared/xpc/XPC.kt | 18 ++- 5 files changed, 195 insertions(+), 54 deletions(-) create mode 100644 connector/composeApp/src/desktopMain/kotlin/tab/test/TestScreenModel.kt diff --git a/connector/composeApp/src/desktopMain/kotlin/InterfaceState.kt b/connector/composeApp/src/desktopMain/kotlin/InterfaceState.kt index 2ae6d5f..07f98bb 100644 --- a/connector/composeApp/src/desktopMain/kotlin/InterfaceState.kt +++ b/connector/composeApp/src/desktopMain/kotlin/InterfaceState.kt @@ -5,6 +5,7 @@ class InterfaceState : KoinComponent { var simConnection = mutableStateOf(false) var projectId: Int? = null var procedureId: Int? = null + var testId: Int? = null val projectSelected: Boolean get() = projectId != null diff --git a/connector/composeApp/src/desktopMain/kotlin/di/CommonModule.kt b/connector/composeApp/src/desktopMain/kotlin/di/CommonModule.kt index b57f2d8..8cc8547 100644 --- a/connector/composeApp/src/desktopMain/kotlin/di/CommonModule.kt +++ b/connector/composeApp/src/desktopMain/kotlin/di/CommonModule.kt @@ -4,11 +4,13 @@ import InterfaceState import io.anthonyberg.connector.shared.ActionTransaction import io.anthonyberg.connector.shared.ProcedureTransaction import io.anthonyberg.connector.shared.ProjectTransaction +import io.anthonyberg.connector.shared.TestTransaction import io.anthonyberg.connector.shared.database.DriverFactory import org.koin.dsl.module import tab.procedure.ActionsScreenModel import tab.procedure.ProcedureScreenModel import tab.project.ProjectsScreenModel +import tab.test.TestScreenModel fun commonModule() = module { single { @@ -26,6 +28,10 @@ fun commonModule() = module { single { ActionTransaction(driverFactory = get()) } + + single { + TestTransaction(driverFactory = get()) + } } fun viewModelModule() = module { @@ -44,4 +50,8 @@ fun viewModelModule() = module { single { ActionsScreenModel(db = get(), interfaceState = get()) } + + single { + TestScreenModel(db = get(), interfaceState = get()) + } } diff --git a/connector/composeApp/src/desktopMain/kotlin/tab/test/TestRun.kt b/connector/composeApp/src/desktopMain/kotlin/tab/test/TestRun.kt index 45f2f8a..b33b57d 100644 --- a/connector/composeApp/src/desktopMain/kotlin/tab/test/TestRun.kt +++ b/connector/composeApp/src/desktopMain/kotlin/tab/test/TestRun.kt @@ -16,36 +16,60 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.koin.getScreenModel import io.anthonyberg.connector.shared.entity.Action -import io.anthonyberg.connector.shared.xpc.XPC -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch class TestRun ( private val actions: List ) : Screen { - private val xpc = XPC() - private val xpcConnected = xpc.connected() - private var tested = mutableStateListOf() - private val initState = getInitState() @Composable override fun Content() { val lazyState = rememberLazyListState(0) var running by remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() + var step by remember { mutableIntStateOf(0) } + + val screenModel = getScreenModel() + val state by screenModel.state.collectAsState() + + if (!running and (tested.size == 0)) { + running = true + screenModel.init() + } + + when (val s = state) { + is TestState.Init -> println("Loading Simulator Tests") + is TestState.NoSimulator -> { + running = false + Text("Could not connect to the simulator!") + return + } + is TestState.Ready -> { + println("Loaded Simulator Tests") + + screenModel.runAction(actions[step]) + } + is TestState.Running -> println("Running Action: ${s.step}") + is TestState.Complete -> { + tested.add(s.pass) + + step += 1 + if (step == actions.size) { + screenModel.end() + } else { + screenModel.runAction(actions[step]) + } + } + is TestState.Idle -> running = false + is TestState.Error -> return Text("An error occurred!") + } Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { - if (!xpcConnected) { - Text("Could not connect to the simulator!") - return - } - // Progress Indicator if (running) { LinearProgressIndicator( @@ -59,7 +83,7 @@ class TestRun ( ) { LazyColumn { items(actions) { action -> - ActionItem(action, initState[action.step]) + ActionItem(action) } } @@ -73,20 +97,11 @@ class TestRun ( ) } } - - if (!running and (tested.size == 0)) { - scope.launch { - running = true - runSteps() - running = false - } - } } @Composable - private fun ActionItem(action: Action, initState: FloatArray) { + private fun ActionItem(action: Action) { ListItem( - overlineContent = { Text("Initial State: ${initState[0]}") }, headlineContent = { Text(action.type) }, supportingContent = { Text("Goal: ${action.goal}") }, leadingContent = { @@ -111,31 +126,4 @@ class TestRun ( HorizontalDivider() } - - private suspend fun runSteps() { - for (action in actions) { - delay(1000L) - - // TODO add try catch - val result = xpc.runChecklist(action.type, action.goal.toInt()) - - // TODO add more detailed results - tested.add(result) - } - } - - private fun getInitState(): Array { - if (!xpc.connected()) { - return Array(actions.size) { FloatArray(0) } - } - - var initDrefs = arrayOf() - for (action in actions) { - initDrefs += action.type - } - - val result = xpc.getStates(initDrefs) - - return result - } } diff --git a/connector/composeApp/src/desktopMain/kotlin/tab/test/TestScreenModel.kt b/connector/composeApp/src/desktopMain/kotlin/tab/test/TestScreenModel.kt new file mode 100644 index 0000000..4db0572 --- /dev/null +++ b/connector/composeApp/src/desktopMain/kotlin/tab/test/TestScreenModel.kt @@ -0,0 +1,130 @@ +package tab.test + +import InterfaceState +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import io.anthonyberg.connector.shared.TestTransaction +import io.anthonyberg.connector.shared.entity.Action +import io.anthonyberg.connector.shared.xpc.XPC +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class TestScreenModel ( + private val db: TestTransaction, + private val interfaceState: InterfaceState +) : StateScreenModel(TestState.Init) { + private val xpc = XPC() + + /** + * Sets up necessary connections + */ + fun init() { + screenModelScope.launch { + mutableState.value = TestState.Init + + val procedureId = interfaceState.procedureId + + // Checks if procedureId is set + if (procedureId == null) { + mutableState.value = TestState.Error + return@launch + } + + // Checks if the simulator is running + val simConnection = xpc.connected() + interfaceState.simConnection.value = simConnection + + if (!simConnection) { + mutableState.value = TestState.NoSimulator + return@launch + } + + // Starts the test in the database + interfaceState.testId = db.startTest(procedureId) + + mutableState.value = TestState.Ready + } + } + + /** + * Run an action in the simulator + * + * @param action Action to be run in the simulator + */ + fun runAction(action: Action) { + screenModelScope.launch { + mutableState.value = TestState.Running(step = action.step) + + val testId = interfaceState.testId + + if (testId == null) { + mutableState.value = TestState.Error + return@launch + } + + // Checks if the simulator is still running + val simConnection = xpc.connected() + interfaceState.simConnection.value = simConnection + + if (!simConnection) { + mutableState.value = TestState.Error + return@launch + } + + // Prerequisite before testing the action + val initDref = xpc.getState(action.type)[0] + val actionTestId = db.startAction( + testId = testId, + actionId = action.id, + initState = initDref.toString() + ) + + delay(1500L) + + // Running the action in the simulator + // TODO deal with action.goal being a String in the database + val goal = action.goal.toInt() + val result = xpc.runChecklist(action.type, goal)[0] + + // Saving result to the database + db.finishAction( + id = actionTestId, + endState = result.toString() + ) + + mutableState.value = TestState.Complete( + step = action.step, + pass = goal.toFloat() == result + ) + } + } + + /** + * To be run after running all the actions in the simulator + */ + fun end() { + screenModelScope.launch { + val testId = interfaceState.testId + + if (testId == null) { + mutableState.value = TestState.Error + return@launch + } + + // Completes the test on the database + db.endTest(testId) + + mutableState.value = TestState.Idle + } + } +} + +sealed class TestState { + data object Init : TestState() + data object NoSimulator : TestState() + data object Error : TestState() + data object Ready : TestState() + data class Running(val step: Int) : TestState() + data class Complete(val step: Int, val pass: Boolean) : TestState() + data object Idle : TestState() +} diff --git a/connector/shared/src/commonMain/kotlin/io/anthonyberg/connector/shared/xpc/XPC.kt b/connector/shared/src/commonMain/kotlin/io/anthonyberg/connector/shared/xpc/XPC.kt index 2193c7f..11c3b13 100644 --- a/connector/shared/src/commonMain/kotlin/io/anthonyberg/connector/shared/xpc/XPC.kt +++ b/connector/shared/src/commonMain/kotlin/io/anthonyberg/connector/shared/xpc/XPC.kt @@ -45,20 +45,32 @@ class XPC { return result } + /** + * Gets the state of a specific Dataref + * + * @param dref Dataref name to get the value for + * @return Value of the Dataref + */ + fun getState(dref: String): FloatArray { + val result = xpc.getDREF(dref) + + return result + } + /** * Sets a dataref in X-Plane to the set goal * * @param dref Dataref name in X-Plane to change the value for * @param goal The value that should be set for the dataref in X-Plane * - * @return `true` if successfully set, `false` otherwise + * @return The state of the Dataref in the simulator */ @Throws(SocketException::class, IOException::class) - fun runChecklist(dref: String, goal: Int) : Boolean { + fun runChecklist(dref: String, goal: Int) : FloatArray { xpc.sendDREF(dref, goal.toFloat()) val result = xpc.getDREF(dref) - return goal.toFloat() == result[0] + return result } }