Joystick & Pid Configuration

Flexible PID configuration and joysticks were added
This commit is contained in:
2024-02-28 23:41:30 +07:00
parent 3517414ec1
commit 5e0f2f1bb7
26 changed files with 534 additions and 66 deletions

10
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="app">
<State />
</entry>
</value>
</component>
</project>

5
.idea/gradle.xml generated
View File

@@ -4,16 +4,15 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>

2
.idea/kotlinc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.6.10" />
<option name="version" value="1.8.10" />
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

View File

@@ -1,8 +1,6 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp").version("1.6.10-1.0.4")
id("org.jlleitschuh.gradle.ktlint").version("12.0.3")
}
android {
@@ -65,6 +63,7 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1")
implementation("androidx.navigation:navigation-compose:2.6.0")
implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
implementation("com.github.manalkaff:JetStick:1.2")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")

View File

@@ -40,12 +40,12 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Testblue"
android:theme="@style/Theme.Main"
tools:targetApi="31">
<activity
android:name="com.helible.pilot.MainActivity"
android:exported="true"
android:theme="@style/Theme.Testblue">
android:theme="@style/Theme.Main">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -6,10 +6,8 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -18,8 +16,11 @@ import androidx.navigation.compose.NavHost
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.console.ConsolePage
import com.helible.pilot.components.deviceScreen.DeviceControlScreen
import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList
import com.helible.pilot.components.pidSettings.PidSettingsPage
import com.helible.pilot.components.scannerScreen.ScannerScreen
import com.helible.pilot.permissions.PermissionsLauncher
import com.helible.pilot.permissions.PermissionsRequest
@@ -141,13 +142,10 @@ class MainActivity : ComponentActivity() {
}
composable("console/{title}")
{ backStackEntry ->
NotImplementedPage(
ConsolePage(
title = backStackEntry.arguments?.getString("title") ?: "null",
navigateBack = { navController.popBackStack() }
)
Button(onClick = { bluetoothViewModel.sendHelloWorld() }) {
Text("Click me!")
}
}
composable("codeblocks/{title}")
{ backStackEntry ->
@@ -174,10 +172,20 @@ class MainActivity : ComponentActivity() {
}
composable("pid_settings/{title}")
{ backStackEntry ->
NotImplementedPage(
PidSettingsPage(
title = backStackEntry.arguments?.getString("title") ?: "null",
navigateBack = { navController.popBackStack() }
navigateBack = {
navController.popBackStack()
bluetoothViewModel.clearPidSettings()
},
requestPidSettings = { bluetoothViewModel.requestPidSettings() },
setPidSettings = {settings -> bluetoothViewModel.applyPidSettings(settings)},
deviceState = bluetoothState.deviceState
)
BackHandler {
navController.popBackStack()
bluetoothViewModel.clearPidSettings()
}
}
composable("reports/{title}")
{ backStackEntry ->

View File

@@ -1,4 +1,4 @@
package com.helible.pilot
package com.helible.pilot.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth

View File

@@ -0,0 +1,33 @@
package com.helible.pilot.components.console
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.helible.pilot.components.BlankPage
import com.helible.pilot.dataclasses.DeviceStatus
import com.manalkaff.jetstick.JoyStick
@Composable
fun ConsolePage(
title: String,
navigateBack: () -> Unit
) {
BlankPage(title = title, navigateBack = navigateBack) {
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
JoyStick(
Modifier.padding(30.dp),
size = 150.dp,
dotSize = 30.dp
){ x: Float, y: Float ->
Log.d("JoyStick", "$x, $y")
}
}
}
}

View File

@@ -0,0 +1,283 @@
package com.helible.pilot.components.pidSettings
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import com.helible.pilot.components.BlankPage
import com.helible.pilot.dataclasses.DeviceState
import com.helible.pilot.dataclasses.DeviceStatus
import com.helible.pilot.dataclasses.PidParams
import com.helible.pilot.dataclasses.PidSettings
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PidSettingsPage(
title: String,
navigateBack: () -> Unit,
requestPidSettings: () -> Unit,
setPidSettings: (PidSettings) -> Unit,
deviceState: DeviceState?,
) {
BlankPage(title = title, navigateBack = navigateBack) {
var pValue by remember { mutableStateOf("") }
var iValue by remember { mutableStateOf("") }
var dValue by remember { mutableStateOf("") }
var selectedRegulator by remember { mutableStateOf(1) }
val dropdownMenuItems = listOf("PID 1", "PID 2", "PID 3")
LaunchedEffect(null) {
requestPidSettings()
}
LaunchedEffect(deviceState?.pidSettings) {
if (deviceState?.pidSettings != null) {
val pidSettings = deviceState.pidSettings
when(selectedRegulator){
1 -> {
pidSettings.p1.p.toString().also { pValue = it }
pidSettings.p1.i.toString().also { iValue = it }
pidSettings.p1.d.toString().also { dValue = it }
}
2 -> {
pidSettings.p2.p.toString().also { pValue = it }
pidSettings.p2.i.toString().also { iValue = it }
pidSettings.p2.d.toString().also { dValue = it }
}
3 -> {
pidSettings.p3.p.toString().also { pValue = it }
pidSettings.p3.i.toString().also { iValue = it }
pidSettings.p3.d.toString().also { dValue = it }
}
}
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
)
{
if (deviceState?.status != DeviceStatus.Idle) {
Text(
text = "Этот раздел доступен только с подключенным заряженным бездействующим устройством.",
textAlign = TextAlign.Center
)
} else if (deviceState.pidSettings == null) {
Column {
CircularProgressIndicator(modifier = Modifier.padding(10.dp))
Text(text = "Синхронизация...")
}
} else {
val pidSettings = deviceState.pidSettings
OutlinedDropdownMenu(
label = "ПИД регулятор",
suggestions = dropdownMenuItems,
onChange = { selected ->
Log.i("BluetoothVM", selected)
when (dropdownMenuItems.indexOf(selected)) {
0 -> {
selectedRegulator = 1
pidSettings.p1.p.toString().also { pValue = it }
pidSettings.p1.i.toString().also { iValue = it }
pidSettings.p1.d.toString().also { dValue = it }
}
1 -> {
selectedRegulator = 2
pidSettings.p2.p.toString().also { pValue = it }
pidSettings.p2.i.toString().also { iValue = it }
pidSettings.p2.d.toString().also { dValue = it }
}
2 -> {
selectedRegulator = 3
pidSettings.p3.p.toString().also { pValue = it }
pidSettings.p3.i.toString().also { iValue = it }
pidSettings.p3.d.toString().also { dValue = it }
}
}
},
modifier = Modifier.padding(10.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier.padding(10.dp)
) {
OutlinedTextField(
value = pValue,
onValueChange = { pValue = it },
label = { Text(text = "P") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.weight(1f)
)
OutlinedTextField(
value = iValue,
onValueChange = { iValue = it },
label = { Text(text = "I") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.weight(1f)
)
OutlinedTextField(
value = dValue,
onValueChange = { dValue = it },
label = { Text(text = "D") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.weight(1f)
)
}
val p = pValue
val i = iValue
val d = dValue
Button(
onClick = {
when (selectedRegulator) {
1 -> {
val newPidSettings = pidSettings.copy(
p1 = PidParams(p.toFloat(), i.toFloat(), d.toFloat())
)
setPidSettings(newPidSettings)
}
2 -> {
val newPidSettings = pidSettings.copy(
p2 = PidParams(p.toFloat(), i.toFloat(), d.toFloat())
)
setPidSettings(newPidSettings)
}
3 -> {
val newPidSettings = pidSettings.copy(
p3 = PidParams(p.toFloat(), i.toFloat(), d.toFloat())
)
setPidSettings(newPidSettings)
}
}
},
enabled = isValidValue(p) && isValidValue(i) && isValidValue(d)
) {
Text(text = "Применить")
}
}
}
}
}
private fun isValidValue(k: String): Boolean {
return k.toFloatOrNull() != null && k.toFloat() >= 0f && k.toFloat() <= 2f
}
@Preview(showBackground = true)
@Composable
fun PidSettingsPreview() {
PidSettingsPage(
title = "Настройки ПИД регуляторов",
navigateBack = { },
requestPidSettings = { },
setPidSettings = {},
deviceState = DeviceState(
status = DeviceStatus.Idle,
pidSettings = PidSettings(
PidParams(1f, 1f, 1f),
PidParams(1f, 1f, 1f),
PidParams(1f, 1f, 1f)
)
)
)
}
@Preview(showBackground = true)
@Composable
fun DropdownDemo() {
OutlinedDropdownMenu(label = "", suggestions = listOf("A", "B"), onChange = {})
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OutlinedDropdownMenu(
label: String,
suggestions: List<String>,
onChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
var expanded by remember { mutableStateOf(false) }
var selectedText by remember { mutableStateOf(suggestions.first()) }
var textfieldSize by remember { mutableStateOf(Size.Zero) }
val icon = if (expanded)
Icons.Filled.KeyboardArrowUp
else
Icons.Filled.KeyboardArrowDown
Column(modifier = modifier) {
OutlinedTextField(
value = selectedText,
onValueChange = {
selectedText = it
},
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
// This value is used to assign to the DropDown the same width
textfieldSize = coordinates.size.toSize()
},
label = { Text(label) },
trailingIcon = {
Icon(icon, "contentDescription",
Modifier.clickable { expanded = !expanded })
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(with(LocalDensity.current) { textfieldSize.width.toDp() })
) {
suggestions.forEach { label ->
DropdownMenuItem(onClick = {
selectedText = label
expanded = false
onChange(selectedText)
},
text = { Text(label) }
)
}
}
}
}

View File

@@ -16,7 +16,7 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import com.helible.pilot.viewmodels.BluetoothDataTransferService
import com.helible.pilot.dataclasses.BluetoothDeviceDomain
import com.helible.pilot.dataclasses.DeviceState
import com.helible.pilot.dataclasses.GeneralMessage
import com.helible.pilot.receivers.BluetoothAdapterStateReceiver
import com.helible.pilot.receivers.BluetoothStateReceiver
import kotlinx.coroutines.CoroutineScope
@@ -40,7 +40,7 @@ import java.util.UUID
sealed interface ConnectionResult {
object ConnectionEstablished : ConnectionResult
data class TransferSucceded(val message: DeviceState) : ConnectionResult
data class TransferSucceded(val message: GeneralMessage) : ConnectionResult
data class Error(val message: String) : ConnectionResult
}
@@ -241,7 +241,7 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
} catch (e: IOException) {
socket.close()
currentClientSocket = null
Log.e("BluetoothController", "I/O exception: e")
Log.e("BluetoothController", "I/O exception: ${e.message}")
emit(ConnectionResult.Error("Connection was interrupted"))
}
}

View File

@@ -1,9 +0,0 @@
package com.helible.pilot.controllers
data class DeviceState(
val isHandshakeWaiting: Boolean = true,
val isIMUCalibrating: Boolean = false,
val flightMode: Boolean = false,
val batteryCharge: Int?,
val flightHeight: Float?
)

View File

@@ -12,4 +12,5 @@ data class DeviceState(
@Json(name = "p") val pitch: Float = 0f,
@Json(name = "r") val roll: Float = 0f,
@Json(name = "zIn") val zInertial: Float = 0f,
val pidSettings: PidSettings? = null
)

View File

@@ -0,0 +1,7 @@
package com.helible.pilot.dataclasses
// This dataclass provide message content with its type without any markers
data class GeneralMessage(
val type: MessageType,
val data: String
)

View File

@@ -0,0 +1,6 @@
package com.helible.pilot.dataclasses
enum class MessageType {
UpdateMessage,
PidSettings
}

View File

@@ -0,0 +1,10 @@
package com.helible.pilot.dataclasses
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PidParams (
val p: Float,
val i: Float,
val d: Float
)

View File

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

View File

@@ -0,0 +1,10 @@
package com.helible.pilot.dataclasses
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PidSettings (
val p1: PidParams,
val p2: PidParams,
val p3: PidParams
)

View File

@@ -0,0 +1,6 @@
package com.helible.pilot.dataclasses
data class StickPosition (
val x: Float,
val y: Float
)

View File

@@ -0,0 +1,5 @@
package com.helible.pilot.exceptions
import java.io.IOException
class TransferFailedException : IOException("Reading incoming data failed")

View File

@@ -4,6 +4,11 @@ import android.bluetooth.BluetoothSocket
import android.util.Log
import com.helible.pilot.dataclasses.DeviceState
import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter
import com.helible.pilot.dataclasses.GeneralMessage
import com.helible.pilot.dataclasses.MessageType
import com.helible.pilot.dataclasses.PidSettings
import com.helible.pilot.exceptions.TransferFailedException
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonEncodingException
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
@@ -16,19 +21,18 @@ import kotlinx.coroutines.withContext
import java.io.IOException
class TransferFailedException : IOException("Reading incoming data failed")
const val maxPackageSize = 512; // bytes
@ExperimentalStdlibApi
class BluetoothDataTransferService(
private val socket: BluetoothSocket,
) {
fun listenForIncomingMessages(): Flow<DeviceState> {
val moshi = Moshi.Builder().add(DeviceStatusJsonAdapter()).add(KotlinJsonAdapterFactory()).build()
val deviceStateMessageAdapter = moshi.adapter<DeviceState>()
fun listenForIncomingMessages(): Flow<GeneralMessage> {
return flow {
if (!socket.isConnected)
return@flow
val buffer = ByteArray(512)
val buffer = ByteArray(maxPackageSize)
while (true) {
val byteCount: Int = try {
socket.inputStream.read(buffer)
@@ -36,23 +40,17 @@ class BluetoothDataTransferService(
Log.e("BluetoothController", "Failed to receive incoming data")
throw TransferFailedException()
}
var messageData: String = buffer.decodeToString(endIndex = byteCount)
val messageType: MessageType? = MessageType.values()
.elementAtOrNull(messageData.split(";")[0].toInt())
val messageData: String = buffer.decodeToString(endIndex = byteCount)
if (!messageData.endsWith("\n\r")) {
if (messageData.endsWith("\n\r") && messageType != null) {
messageData = messageData.dropLast(2).split(";")[1]
emit(GeneralMessage(messageType, messageData))
Log.d("BluetoothController", "Received: $messageData")
} else {
Log.i("BluetoothController", "Package end isn't valid.")
Log.i("BluetoothController", messageData)
} else {
val messageJson = messageData.dropLast(2)
try {
val deviceState = deviceStateMessageAdapter.fromJson(messageJson)!!
emit(deviceState)
Log.i("BluetoothController", "Received: $deviceState")
} catch (e: NullPointerException) {
Log.e("BluetoothController", "Nullable message received: $messageJson")
} catch (e: JsonEncodingException) {
Log.e("BluetoothController", "Invalid message received: $messageJson")
}
}
}
}.flowOn(Dispatchers.IO)

View File

@@ -8,8 +8,14 @@ import com.helible.pilot.controllers.ConnectionResult
import com.helible.pilot.dataclasses.BluetoothDevice
import com.helible.pilot.dataclasses.BluetoothUiState
import com.helible.pilot.dataclasses.ChangedDeviceStatus
import com.helible.pilot.dataclasses.DeviceState
import com.helible.pilot.dataclasses.DeviceStatus
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.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonEncodingException
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import kotlinx.coroutines.Job
@@ -25,7 +31,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.time.LocalTime
class BluetoothViewModel(
private val bluetoothController: BluetoothController,
@@ -46,7 +51,10 @@ class BluetoothViewModel(
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), _state.value)
private var deviceConnectionJob: Job? = null
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).add(DeviceStatusJsonAdapter()).build()
private val newStatusMessageAdapter = moshi.adapter(ChangedDeviceStatus::class.java)
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)
init {
bluetoothController.isConnected.onEach { isConnected ->
@@ -94,10 +102,35 @@ class BluetoothViewModel(
}
is ConnectionResult.TransferSucceded -> {
_state.update {
it.copy(
deviceState = result.message
)
try {
when (result.message.type) {
MessageType.PidSettings -> {
val newPidSettings =
pidSittingsMessageAdapter.fromJson(result.message.data)
_state.update {
it.copy(
deviceState = it.deviceState?.copy(pidSettings = newPidSettings)
)
}
}
MessageType.UpdateMessage -> {
val newDeviceState =
deviceStateMessageAdapter.fromJson(result.message.data)
if (newDeviceState != null) {
_state.update {
it.copy(
deviceState = newDeviceState.copy(pidSettings = it.deviceState?.pidSettings)
)
}
}
}
}
} catch (e: JsonDataException) {
Log.e("BluetoothVM", "Failed to parse message: ${result.message.data}")
} catch (e: JsonEncodingException) {
Log.e("BluetoothVM", "Failed to decode message: ${result.message.data}")
}
}
@@ -113,13 +146,18 @@ class BluetoothViewModel(
}
}
.catch { throwable ->
Log.e("BluetoothController", "Error occured while data transfer: ${throwable.message}")
Log.e(
"BluetoothController",
"Error occured while data transfer: ${throwable.message}"
)
bluetoothController.closeConnection()
_state.update { it.copy(
isConnected = false,
isConnecting = false,
deviceState = null
) }
_state.update {
it.copy(
isConnected = false,
isConnecting = false,
deviceState = null
)
}
}
.launchIn(viewModelScope)
}
@@ -176,15 +214,59 @@ class BluetoothViewModel(
fun startImuCalibration() {
viewModelScope.launch {
val message = newStatusMessageAdapter.toJson(
val message = statusMessageAdapter.toJson(
ChangedDeviceStatus(DeviceStatus.IsImuCalibration)
) + "\n\r"
val success = bluetoothController.trySendMessage(
val isSuccess = bluetoothController.trySendMessage(
message.toByteArray()
)
if(!success) {
if(!isSuccess) {
Log.e("BluetoothVM", "Failed to start IMU calibration: $message")
} else {
_state.update {
it.copy(
deviceState = it.deviceState?.copy(status = DeviceStatus.IsImuCalibration)
)
}
}
}
}
fun requestPidSettings() {
viewModelScope.launch {
val message = pidSittingsRequiredMessageAdapter.toJson(PidSettingRequiredMessage(true)) + "\n\r"
Log.i("BluetoothVM", "Requested PID settings: $message")
val isSuccess = bluetoothController.trySendMessage(
message.toByteArray()
)
if(!isSuccess) {
Log.e("BluetoothVM", "Failed to request PID settings: $message")
}
}
}
fun applyPidSettings(pidSettings: PidSettings) {
viewModelScope.launch {
val message = pidSittingsMessageAdapter.toJson(pidSettings) + "\n\r"
val isSuccess = bluetoothController.trySendMessage(message.toByteArray())
if(!isSuccess) {
Log.e("BluetoothVM", "Failed to request PID settings: $message")
_state.update {
it.copy(errorMessage = "Не удалось обновить значения PID")
}
} else {
_state.update {
it.copy(deviceState = it.deviceState?.copy(pidSettings = pidSettings))
}
}
}
}
fun clearPidSettings() {
Log.i("BluetoothVM", "PidSettings cleared")
_state.update {
it.copy(deviceState = it.deviceState?.copy(pidSettings = null))
}
Log.i("BluetoothVM", "PidSettings: ${_state.value.deviceState?.pidSettings}")
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Testblue" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Theme.Main" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.0" apply false
id("com.android.application") version "8.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("org.jetbrains.kotlin.jvm") version "1.8.10" apply false
}

View File

@@ -1,6 +1,6 @@
#Sun Aug 13 15:00:54 KRAT 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -3,6 +3,7 @@ pluginManagement {
google()
mavenCentral()
gradlePluginPortal()
maven(url = "https://jitpack.io")
}
}
dependencyResolutionManagement {
@@ -10,6 +11,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven(url = "https://jitpack.io")
}
}