Compare commits

...

2 Commits

Author SHA1 Message Date
027116e18e PID contrllers settings page was added 2024-03-08 23:07:18 +07:00
c8abfd94c3 Rotors test page was added 2024-03-08 23:05:38 +07:00
8 changed files with 319 additions and 53 deletions

View File

@@ -17,6 +17,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.helible.pilot.components.CalibrationPage import com.helible.pilot.components.CalibrationPage
import com.helible.pilot.components.NotImplementedPage import com.helible.pilot.components.NotImplementedPage
import com.helible.pilot.components.RotorsTestPage
import com.helible.pilot.components.console.ConsolePage import com.helible.pilot.components.console.ConsolePage
import com.helible.pilot.components.deviceScreen.DeviceControlScreen import com.helible.pilot.components.deviceScreen.DeviceControlScreen
import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList
@@ -62,6 +63,7 @@ class MainActivity : ComponentActivity() {
val bluetoothState by bluetoothViewModel.state.collectAsState() val bluetoothState by bluetoothViewModel.state.collectAsState()
val selectedDevice by bluetoothViewModel.selectedDevice.collectAsState() val selectedDevice by bluetoothViewModel.selectedDevice.collectAsState()
val rotorsDuty by bluetoothViewModel.rotorsDuty.collectAsState()
LaunchedEffect(key1 = null) { LaunchedEffect(key1 = null) {
permissionLauncher.launch() permissionLauncher.launch()
@@ -165,10 +167,23 @@ class MainActivity : ComponentActivity() {
} }
composable("motor_test/{title}") composable("motor_test/{title}")
{ backStackEntry -> { backStackEntry ->
NotImplementedPage( RotorsTestPage(
title = backStackEntry.arguments?.getString("title") ?: "null", 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}") composable("pid_settings/{title}")
{ backStackEntry -> { backStackEntry ->

View 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*/ }
)
}
}

View File

@@ -18,6 +18,7 @@ import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -30,12 +31,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize import androidx.compose.ui.unit.toSize
import com.helible.pilot.R
import com.helible.pilot.components.BlankPage import com.helible.pilot.components.BlankPage
import com.helible.pilot.dataclasses.DeviceState import com.helible.pilot.dataclasses.DeviceState
import com.helible.pilot.dataclasses.DeviceStatus import com.helible.pilot.dataclasses.DeviceStatus
@@ -55,8 +58,9 @@ fun PidSettingsPage(
var pValue by remember { mutableStateOf("") } var pValue by remember { mutableStateOf("") }
var iValue by remember { mutableStateOf("") } var iValue by remember { mutableStateOf("") }
var dValue by remember { mutableStateOf("") } var dValue by remember { mutableStateOf("") }
var selectedRegulator by remember { mutableStateOf(1) } var selectedRegulator by remember { mutableStateOf("") }
val dropdownMenuItems = listOf("PID 1", "PID 2", "PID 3") val dropdownMenuItems =
listOf("Контроллер высоты", "Контроллер крена", "Контроллер рысканья")
LaunchedEffect(null) { LaunchedEffect(null) {
requestPidSettings() requestPidSettings()
@@ -64,21 +68,23 @@ fun PidSettingsPage(
LaunchedEffect(deviceState?.pidSettings) { LaunchedEffect(deviceState?.pidSettings) {
if (deviceState?.pidSettings != null) { if (deviceState?.pidSettings != null) {
val pidSettings = deviceState.pidSettings val pidSettings = deviceState.pidSettings
when(selectedRegulator){ when (selectedRegulator) {
1 -> { dropdownMenuItems[0] -> {
pidSettings.p1.p.toString().also { pValue = it } pidSettings.heightControllerParams.p.toString().also { pValue = it }
pidSettings.p1.i.toString().also { iValue = it } pidSettings.heightControllerParams.i.toString().also { iValue = it }
pidSettings.p1.d.toString().also { dValue = it } pidSettings.heightControllerParams.d.toString().also { dValue = it }
} }
2 -> {
pidSettings.p2.p.toString().also { pValue = it } dropdownMenuItems[1] -> {
pidSettings.p2.i.toString().also { iValue = it } pidSettings.yawControllerParams.p.toString().also { pValue = it }
pidSettings.p2.d.toString().also { dValue = it } pidSettings.yawControllerParams.i.toString().also { iValue = it }
pidSettings.yawControllerParams.d.toString().also { dValue = it }
} }
3 -> {
pidSettings.p3.p.toString().also { pValue = it } dropdownMenuItems[2] -> {
pidSettings.p3.i.toString().also { iValue = it } pidSettings.pitchControllerParams.p.toString().also { pValue = it }
pidSettings.p3.d.toString().also { dValue = it } pidSettings.pitchControllerParams.i.toString().also { iValue = it }
pidSettings.pitchControllerParams.d.toString().also { dValue = it }
} }
} }
} }
@@ -87,7 +93,6 @@ fun PidSettingsPage(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(10.dp)
) )
{ {
if (deviceState?.status != DeviceStatus.Idle) { if (deviceState?.status != DeviceStatus.Idle) {
@@ -96,13 +101,32 @@ fun PidSettingsPage(
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} else if (deviceState.pidSettings == null) { } else if (deviceState.pidSettings == null) {
Column {
CircularProgressIndicator(modifier = Modifier.padding(10.dp)) CircularProgressIndicator(modifier = Modifier.padding(10.dp))
Text(text = "Синхронизация...") Text(text = "Синхронизация...")
}
} else { } else {
val pidSettings = deviceState.pidSettings val pidSettings = deviceState.pidSettings
Column(modifier = Modifier.padding(horizontal = 10.dp).padding(bottom = 10.dp)) {
Text(
"Рекомендации по настройке ПИД регуляторов",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.padding(vertical = 10.dp)
)
Text(
text = LocalContext.current.getString(R.string.p_pid_value_description),
style = MaterialTheme.typography.bodyMedium
)
Text(
text = LocalContext.current.getString(R.string.i_pid_value_description),
style = MaterialTheme.typography.bodyMedium
)
Text(
text = LocalContext.current.getString(R.string.d_pid_value_description),
style = MaterialTheme.typography.bodyMedium
)
}
OutlinedDropdownMenu( OutlinedDropdownMenu(
label = "ПИД регулятор", label = "ПИД регулятор",
suggestions = dropdownMenuItems, suggestions = dropdownMenuItems,
@@ -110,22 +134,24 @@ fun PidSettingsPage(
Log.i("BluetoothVM", selected) Log.i("BluetoothVM", selected)
when (dropdownMenuItems.indexOf(selected)) { when (dropdownMenuItems.indexOf(selected)) {
0 -> { 0 -> {
selectedRegulator = 1 selectedRegulator = dropdownMenuItems[0]
pidSettings.p1.p.toString().also { pValue = it } pidSettings.heightControllerParams.p.toString().also { pValue = it }
pidSettings.p1.i.toString().also { iValue = it } pidSettings.heightControllerParams.i.toString().also { iValue = it }
pidSettings.p1.d.toString().also { dValue = it } pidSettings.heightControllerParams.d.toString().also { dValue = it }
} }
1 -> { 1 -> {
selectedRegulator = 2 selectedRegulator = dropdownMenuItems[1]
pidSettings.p2.p.toString().also { pValue = it } pidSettings.yawControllerParams.p.toString().also { pValue = it }
pidSettings.p2.i.toString().also { iValue = it } pidSettings.yawControllerParams.i.toString().also { iValue = it }
pidSettings.p2.d.toString().also { dValue = it } pidSettings.yawControllerParams.d.toString().also { dValue = it }
} }
2 -> { 2 -> {
selectedRegulator = 3 selectedRegulator = dropdownMenuItems[2]
pidSettings.p3.p.toString().also { pValue = it } pidSettings.pitchControllerParams.p.toString().also { pValue = it }
pidSettings.p3.i.toString().also { iValue = it } pidSettings.pitchControllerParams.i.toString().also { iValue = it }
pidSettings.p3.d.toString().also { dValue = it } pidSettings.pitchControllerParams.d.toString().also { dValue = it }
} }
} }
}, },
@@ -167,23 +193,35 @@ fun PidSettingsPage(
Button( Button(
onClick = { onClick = {
when (selectedRegulator) { when (selectedRegulator) {
1 -> { dropdownMenuItems[0] -> {
val newPidSettings = pidSettings.copy( val newPidSettings = pidSettings.copy(
p1 = PidParams(p.toFloat(), i.toFloat(), d.toFloat()) heightControllerParams = PidParams(
p.toFloat(),
i.toFloat(),
d.toFloat()
)
) )
setPidSettings(newPidSettings) setPidSettings(newPidSettings)
} }
2 -> { dropdownMenuItems[1] -> {
val newPidSettings = pidSettings.copy( val newPidSettings = pidSettings.copy(
p2 = PidParams(p.toFloat(), i.toFloat(), d.toFloat()) yawControllerParams = PidParams(
p.toFloat(),
i.toFloat(),
d.toFloat()
)
) )
setPidSettings(newPidSettings) setPidSettings(newPidSettings)
} }
3 -> { dropdownMenuItems[2] -> {
val newPidSettings = pidSettings.copy( val newPidSettings = pidSettings.copy(
p3 = PidParams(p.toFloat(), i.toFloat(), d.toFloat()) pitchControllerParams = PidParams(
p.toFloat(),
i.toFloat(),
d.toFloat()
)
) )
setPidSettings(newPidSettings) setPidSettings(newPidSettings)
} }
@@ -199,7 +237,7 @@ fun PidSettingsPage(
} }
private fun isValidValue(k: String): Boolean { private fun isValidValue(k: String): Boolean {
return k.toFloatOrNull() != null && k.toFloat() >= 0f && k.toFloat() <= 2f return k.toFloatOrNull() != null && k.toFloat() >= 0f && k.toFloat() <= 15f
} }
@Preview(showBackground = true) @Preview(showBackground = true)

View File

@@ -1,10 +1,11 @@
package com.helible.pilot.dataclasses package com.helible.pilot.dataclasses
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class PidSettings ( data class PidSettings (
val p1: PidParams, @Json(name = "p1") val heightControllerParams: PidParams,
val p2: PidParams, @Json(name = "p2") val yawControllerParams: PidParams,
val p3: PidParams @Json(name = "p3") val pitchControllerParams: PidParams
) )

View File

@@ -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
)

View File

@@ -0,0 +1,8 @@
package com.helible.pilot.dataclasses
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class StopMessage(
val stop: Boolean = true
)

View File

@@ -14,11 +14,14 @@ import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter
import com.helible.pilot.dataclasses.MessageType import com.helible.pilot.dataclasses.MessageType
import com.helible.pilot.dataclasses.PidSettingRequiredMessage import com.helible.pilot.dataclasses.PidSettingRequiredMessage
import com.helible.pilot.dataclasses.PidSettings 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.JsonDataException
import com.squareup.moshi.JsonEncodingException import com.squareup.moshi.JsonEncodingException
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@@ -26,6 +29,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
@@ -49,14 +53,28 @@ class BluetoothViewModel(
pairedBluetoothDevices = pairedDevices pairedBluetoothDevices = pairedDevices
) )
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), _state.value) }.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 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 statusMessageAdapter = moshi.adapter(ChangedDeviceStatus::class.java)
private val deviceStateMessageAdapter = moshi.adapter(DeviceState::class.java) private val deviceStateMessageAdapter = moshi.adapter(DeviceState::class.java)
private val pidSittingsMessageAdapter = moshi.adapter(PidSettings::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 { companion object {
const val messageDelimeter = "\n" const val messageDelimiter = "\n"
const val telemetryPauseDuractionMs: Long = 100
} }
init { init {
@@ -210,11 +228,11 @@ class BluetoothViewModel(
viewModelScope.launch { viewModelScope.launch {
val message = statusMessageAdapter.toJson( val message = statusMessageAdapter.toJson(
ChangedDeviceStatus(DeviceStatus.IsImuCalibration) ChangedDeviceStatus(DeviceStatus.IsImuCalibration)
) + messageDelimeter ) + messageDelimiter
val isSuccess = bluetoothController.trySendMessage( val isSuccess = bluetoothController.trySendMessage(
message.toByteArray() message.toByteArray()
) )
if(!isSuccess) { if (!isSuccess) {
Log.e("BluetoothVM", "Failed to start IMU calibration: $message") Log.e("BluetoothVM", "Failed to start IMU calibration: $message")
} else { } else {
_state.update { _state.update {
@@ -228,12 +246,13 @@ class BluetoothViewModel(
fun requestPidSettings() { fun requestPidSettings() {
viewModelScope.launch { viewModelScope.launch {
val message = pidSittingsRequiredMessageAdapter.toJson(PidSettingRequiredMessage(true)) + messageDelimeter val message =
pidSittingsRequiredMessageAdapter.toJson(PidSettingRequiredMessage(true)) + messageDelimiter
Log.i("BluetoothVM", "Requested PID settings: $message") Log.i("BluetoothVM", "Requested PID settings: $message")
val isSuccess = bluetoothController.trySendMessage( val isSuccess = bluetoothController.trySendMessage(
message.toByteArray() message.toByteArray()
) )
if(!isSuccess) { if (!isSuccess) {
Log.e("BluetoothVM", "Failed to request PID settings: $message") Log.e("BluetoothVM", "Failed to request PID settings: $message")
} }
} }
@@ -241,9 +260,9 @@ class BluetoothViewModel(
fun applyPidSettings(pidSettings: PidSettings) { fun applyPidSettings(pidSettings: PidSettings) {
viewModelScope.launch { viewModelScope.launch {
val message = pidSittingsMessageAdapter.toJson(pidSettings) + messageDelimeter val message = pidSittingsMessageAdapter.toJson(pidSettings) + messageDelimiter
val isSuccess = bluetoothController.trySendMessage(message.toByteArray()) val isSuccess = bluetoothController.trySendMessage(message.toByteArray())
if(!isSuccess) { if (!isSuccess) {
Log.e("BluetoothVM", "Failed to request PID settings: $message") Log.e("BluetoothVM", "Failed to request PID settings: $message")
_state.update { _state.update {
it.copy(errorMessage = "Не удалось обновить значения PID") it.copy(errorMessage = "Не удалось обновить значения PID")
@@ -263,4 +282,58 @@ class BluetoothViewModel(
} }
Log.i("BluetoothVM", "PidSettings: ${_state.value.deviceState?.pidSettings}") 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) }
}
}
}
} }

View File

@@ -1,4 +1,7 @@
<resources> <resources>
<string name="app_name">Digital Pilot</string> <string name="app_name">Digital Pilot</string>
<string name="calibration_description">Расположите устройство на ровной горизонтальной поверхности, чтобы сани вертолета полностью лежали на ней. Нажмите кнопку калибровки ниже и ждите её окончания, не создавая тряски.</string> <string name="calibration_description">Расположите устройство на ровной горизонтальной поверхности, чтобы сани вертолета полностью лежали на ней. Нажмите кнопку калибровки ниже и ждите её окончания, не создавая тряски.</string>
<string name="p_pid_value_description">Сначала подберите значение коэффицента P, которое балансирует между слишком низкой и слишком высокой чувствительностью.</string>
<string name="i_pid_value_description">Затем подберите значение коэффицента I, которое уберёт нежелательный дрейв, но не повлияет на отзывчивость.</string>
<string name="d_pid_value_description">После установите значение коэффицента D таким образом, чтобы обеспечить более стабильное и плавное управление.</string>
</resources> </resources>