Rotors test page was added
This commit is contained in:
@@ -17,6 +17,7 @@ import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.helible.pilot.components.CalibrationPage
|
||||
import com.helible.pilot.components.NotImplementedPage
|
||||
import com.helible.pilot.components.RotorsTestPage
|
||||
import com.helible.pilot.components.console.ConsolePage
|
||||
import com.helible.pilot.components.deviceScreen.DeviceControlScreen
|
||||
import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList
|
||||
@@ -62,6 +63,7 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
val bluetoothState by bluetoothViewModel.state.collectAsState()
|
||||
val selectedDevice by bluetoothViewModel.selectedDevice.collectAsState()
|
||||
val rotorsDuty by bluetoothViewModel.rotorsDuty.collectAsState()
|
||||
|
||||
LaunchedEffect(key1 = null) {
|
||||
permissionLauncher.launch()
|
||||
@@ -165,10 +167,23 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
composable("motor_test/{title}")
|
||||
{ backStackEntry ->
|
||||
NotImplementedPage(
|
||||
RotorsTestPage(
|
||||
title = backStackEntry.arguments?.getString("title") ?: "null",
|
||||
navigateBack = { navController.popBackStack() }
|
||||
rotorsDuty = rotorsDuty,
|
||||
setRotorsDuty = { bluetoothViewModel.setRotorsDuty(it) },
|
||||
startTelemetrySending = { bluetoothViewModel.startRotorsConfigurationTelemetry() },
|
||||
stopRotors = { bluetoothViewModel.stopRotors() },
|
||||
navigateBack = {
|
||||
navController.popBackStack()
|
||||
bluetoothViewModel.stopRotorsConfigurationTelemetry()
|
||||
bluetoothViewModel.stopRotors()
|
||||
}
|
||||
)
|
||||
BackHandler {
|
||||
navController.popBackStack()
|
||||
bluetoothViewModel.stopRotorsConfigurationTelemetry()
|
||||
bluetoothViewModel.stopRotors()
|
||||
}
|
||||
}
|
||||
composable("pid_settings/{title}")
|
||||
{ backStackEntry ->
|
||||
|
||||
118
app/src/main/java/com/helible/pilot/components/RotorsTestPage.kt
Normal file
118
app/src/main/java/com/helible/pilot/components/RotorsTestPage.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
package com.helible.pilot.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.helible.pilot.dataclasses.RotorsDuty
|
||||
import com.helible.pilot.ui.theme.TestblueTheme
|
||||
|
||||
@Composable
|
||||
fun RotorsTestPage(
|
||||
title: String,
|
||||
rotorsDuty: RotorsDuty,
|
||||
setRotorsDuty: (duty: RotorsDuty) -> Unit,
|
||||
startTelemetrySending: () -> Unit,
|
||||
stopRotors: () -> Unit,
|
||||
navigateBack: () -> Unit,
|
||||
) {
|
||||
BlankPage(title = title, navigateBack = navigateBack) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
LaunchedEffect(null) {
|
||||
startTelemetrySending()
|
||||
}
|
||||
Text(
|
||||
text = "R1",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 10.dp)
|
||||
)
|
||||
Slider(
|
||||
value = rotorsDuty.r1.toFloat(),
|
||||
onValueChange = { setRotorsDuty(rotorsDuty.copy(r1 = it.toInt().toShort())) },
|
||||
valueRange = 0f..5000f,
|
||||
steps = 10
|
||||
)
|
||||
Text(
|
||||
text = "При перемещении слайдера вправо ротор 1 должен вращаться против часовой стрелки, если смотреть сверху.",
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "R2",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 10.dp)
|
||||
)
|
||||
Slider(
|
||||
value = rotorsDuty.r2.toFloat(),
|
||||
onValueChange = { setRotorsDuty(rotorsDuty.copy(r2 = it.toInt().toShort())) },
|
||||
valueRange = -5000f..5000f,
|
||||
steps = 10
|
||||
)
|
||||
Text(
|
||||
text = "При перемещении слайдера вправо ротор 1 должен вращаться по часовой стрелке, если смотреть сверху.",
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "R3",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 10.dp)
|
||||
)
|
||||
Slider(
|
||||
value = rotorsDuty.r3.toFloat(),
|
||||
onValueChange = { setRotorsDuty(rotorsDuty.copy(r3 = it.toInt().toShort())) },
|
||||
valueRange = 0f..5000f,
|
||||
steps = 10
|
||||
)
|
||||
Text(
|
||||
text = "При отклонении слайдера вправо от центра ротор 1 должен вращаться по часовой стрелке, а при отклонении влево - против часовой, если смотреть сверху.",
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
FloatingActionButton(
|
||||
onClick = { stopRotors() },
|
||||
containerColor = Color(245, 47, 7),
|
||||
modifier = Modifier.padding(top = 10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "СТОП",
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = Color.White,
|
||||
modifier = Modifier.padding(10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun RotorsTestPagePreview() {
|
||||
TestblueTheme {
|
||||
RotorsTestPage(
|
||||
title = "Тестирование моторов",
|
||||
rotorsDuty = RotorsDuty(5, 5, 5),
|
||||
setRotorsDuty = { _ -> },
|
||||
startTelemetrySending = {},
|
||||
stopRotors = {},
|
||||
navigateBack = { /*TODO*/ }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.helible.pilot.dataclasses
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RotorsDuty(
|
||||
val r1: Short,
|
||||
val r2: Short,
|
||||
val r3: Short
|
||||
)
|
||||
@@ -14,11 +14,14 @@ import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter
|
||||
import com.helible.pilot.dataclasses.MessageType
|
||||
import com.helible.pilot.dataclasses.PidSettingRequiredMessage
|
||||
import com.helible.pilot.dataclasses.PidSettings
|
||||
import com.helible.pilot.dataclasses.RotorsDuty
|
||||
import com.helible.pilot.dataclasses.StopMessage
|
||||
import com.squareup.moshi.JsonDataException
|
||||
import com.squareup.moshi.JsonEncodingException
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -26,6 +29,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@@ -49,14 +53,28 @@ class BluetoothViewModel(
|
||||
pairedBluetoothDevices = pairedDevices
|
||||
)
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), _state.value)
|
||||
private val _rotorsDuty: MutableStateFlow<RotorsDuty> = MutableStateFlow(RotorsDuty(0, 0, 0))
|
||||
private val _isRotorsTelemetryEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
|
||||
val rotorsDuty: StateFlow<RotorsDuty>
|
||||
get() = _rotorsDuty.asStateFlow()
|
||||
|
||||
private var rotorsTelemetryJob: Job? = null
|
||||
private var deviceConnectionJob: Job? = null
|
||||
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).add(DeviceStatusJsonAdapter()).build()
|
||||
|
||||
private val moshi =
|
||||
Moshi.Builder().add(KotlinJsonAdapterFactory()).add(DeviceStatusJsonAdapter()).build()
|
||||
private val statusMessageAdapter = moshi.adapter(ChangedDeviceStatus::class.java)
|
||||
private val deviceStateMessageAdapter = moshi.adapter(DeviceState::class.java)
|
||||
private val pidSittingsMessageAdapter = moshi.adapter(PidSettings::class.java)
|
||||
private val pidSittingsRequiredMessageAdapter = moshi.adapter(PidSettingRequiredMessage::class.java)
|
||||
private val pidSittingsRequiredMessageAdapter =
|
||||
moshi.adapter(PidSettingRequiredMessage::class.java)
|
||||
private val rotorDutyMessageAdapter = moshi.adapter(RotorsDuty::class.java)
|
||||
private val stopAllRotorsMessageAdapter = moshi.adapter(StopMessage::class.java)
|
||||
|
||||
companion object {
|
||||
const val messageDelimeter = "\n"
|
||||
const val messageDelimiter = "\n"
|
||||
const val telemetryPauseDuractionMs: Long = 100
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -210,11 +228,11 @@ class BluetoothViewModel(
|
||||
viewModelScope.launch {
|
||||
val message = statusMessageAdapter.toJson(
|
||||
ChangedDeviceStatus(DeviceStatus.IsImuCalibration)
|
||||
) + messageDelimeter
|
||||
) + messageDelimiter
|
||||
val isSuccess = bluetoothController.trySendMessage(
|
||||
message.toByteArray()
|
||||
)
|
||||
if(!isSuccess) {
|
||||
if (!isSuccess) {
|
||||
Log.e("BluetoothVM", "Failed to start IMU calibration: $message")
|
||||
} else {
|
||||
_state.update {
|
||||
@@ -228,12 +246,13 @@ class BluetoothViewModel(
|
||||
|
||||
fun requestPidSettings() {
|
||||
viewModelScope.launch {
|
||||
val message = pidSittingsRequiredMessageAdapter.toJson(PidSettingRequiredMessage(true)) + messageDelimeter
|
||||
val message =
|
||||
pidSittingsRequiredMessageAdapter.toJson(PidSettingRequiredMessage(true)) + messageDelimiter
|
||||
Log.i("BluetoothVM", "Requested PID settings: $message")
|
||||
val isSuccess = bluetoothController.trySendMessage(
|
||||
message.toByteArray()
|
||||
)
|
||||
if(!isSuccess) {
|
||||
if (!isSuccess) {
|
||||
Log.e("BluetoothVM", "Failed to request PID settings: $message")
|
||||
}
|
||||
}
|
||||
@@ -241,9 +260,9 @@ class BluetoothViewModel(
|
||||
|
||||
fun applyPidSettings(pidSettings: PidSettings) {
|
||||
viewModelScope.launch {
|
||||
val message = pidSittingsMessageAdapter.toJson(pidSettings) + messageDelimeter
|
||||
val message = pidSittingsMessageAdapter.toJson(pidSettings) + messageDelimiter
|
||||
val isSuccess = bluetoothController.trySendMessage(message.toByteArray())
|
||||
if(!isSuccess) {
|
||||
if (!isSuccess) {
|
||||
Log.e("BluetoothVM", "Failed to request PID settings: $message")
|
||||
_state.update {
|
||||
it.copy(errorMessage = "Не удалось обновить значения PID")
|
||||
@@ -263,4 +282,58 @@ class BluetoothViewModel(
|
||||
}
|
||||
Log.i("BluetoothVM", "PidSettings: ${_state.value.deviceState?.pidSettings}")
|
||||
}
|
||||
|
||||
private fun sendRotorsDuty() {
|
||||
viewModelScope.launch {
|
||||
val message = rotorDutyMessageAdapter.toJson(
|
||||
_rotorsDuty.value
|
||||
) + messageDelimiter
|
||||
val isSuccess = bluetoothController.trySendMessage(
|
||||
message.toByteArray()
|
||||
)
|
||||
if (!isSuccess) {
|
||||
Log.e("BluetoothVM", "Failed to send rotors telemetry: $message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startRotorsConfigurationTelemetry() {
|
||||
Log.i("BluetoothVM", "Start send rotors configuration telemetry...")
|
||||
if(_isRotorsTelemetryEnabled.value) return
|
||||
_isRotorsTelemetryEnabled.update { true }
|
||||
flow {
|
||||
while(_isRotorsTelemetryEnabled.value) {
|
||||
emit(Unit)
|
||||
delay(telemetryPauseDuractionMs)
|
||||
}
|
||||
}.onEach{
|
||||
sendRotorsDuty()
|
||||
Log.d("BluetoothVM", "Sended rotors telemetry")
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun stopRotorsConfigurationTelemetry() {
|
||||
Log.i("BluetoothVM", "Stop send rotors configuration periodically...")
|
||||
rotorsTelemetryJob = null
|
||||
_isRotorsTelemetryEnabled.update { false }
|
||||
}
|
||||
|
||||
fun setRotorsDuty(newRotorsDuty: RotorsDuty) {
|
||||
_rotorsDuty.update { newRotorsDuty }
|
||||
}
|
||||
|
||||
fun stopRotors() {
|
||||
viewModelScope.launch {
|
||||
val message = stopAllRotorsMessageAdapter.toJson(StopMessage()) + messageDelimiter
|
||||
val isSuccess = bluetoothController.trySendMessage(message.toByteArray())
|
||||
if (!isSuccess) {
|
||||
Log.e("BluetoothVM", "Failed to stop all rotors: $message")
|
||||
_state.update {
|
||||
it.copy(errorMessage = "Не удалось остановить моторы!")
|
||||
}
|
||||
} else {
|
||||
_rotorsDuty.update { RotorsDuty(0, 0, 0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user