Compare commits
2 Commits
0763c2e1df
...
027116e18e
| Author | SHA1 | Date | |
|---|---|---|---|
| 027116e18e | |||
| c8abfd94c3 |
@@ -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 ->
|
||||||
|
|||||||
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*/ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.helible.pilot.dataclasses
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class StopMessage(
|
||||||
|
val stop: Boolean = true
|
||||||
|
)
|
||||||
@@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user