From 3517414ec10f1da309fdc726572005ad64effeb4 Mon Sep 17 00:00:00 2001 From: gogacoder Date: Fri, 9 Feb 2024 19:28:49 +0700 Subject: [PATCH] Data tranfering improvement --- app/src/main/java/com/helible/pilot/Kproto.kt | 23 ----- .../java/com/helible/pilot/MainActivity.kt | 18 ++-- .../pilot/components/CalibrationPage.kt | 84 +++++++++++++++++++ .../components/deviceScreen/DeviceBadge.kt | 16 +++- .../deviceScreen/DeviceConnectionStatus.kt | 2 + .../pilot/controllers/BluetoothController.kt | 18 ++-- .../pilot/dataclasses/BluetoothUiState.kt | 1 + .../pilot/dataclasses/ChangedDeviceStatus.kt | 6 ++ .../helible/pilot/dataclasses/DeviceState.kt | 15 ++++ .../helible/pilot/dataclasses/DeviceStatus.kt | 21 +++++ .../dataclasses/DeviceStatusJsonAdapter.kt | 22 +++++ .../helible/pilot/dataclasses/MessageTypes.kt | 12 --- .../BluetoothDataTransferService.kt | 44 +++++++--- .../pilot/viewmodels/BluetoothViewModel.kt | 49 +++++++---- .../viewmodels/BluetoothViewModelFactory.kt | 1 + app/src/main/res/values/strings.xml | 1 + 16 files changed, 251 insertions(+), 82 deletions(-) delete mode 100644 app/src/main/java/com/helible/pilot/Kproto.kt create mode 100644 app/src/main/java/com/helible/pilot/components/CalibrationPage.kt create mode 100644 app/src/main/java/com/helible/pilot/dataclasses/ChangedDeviceStatus.kt create mode 100644 app/src/main/java/com/helible/pilot/dataclasses/DeviceState.kt create mode 100644 app/src/main/java/com/helible/pilot/dataclasses/DeviceStatus.kt create mode 100644 app/src/main/java/com/helible/pilot/dataclasses/DeviceStatusJsonAdapter.kt delete mode 100644 app/src/main/java/com/helible/pilot/dataclasses/MessageTypes.kt diff --git a/app/src/main/java/com/helible/pilot/Kproto.kt b/app/src/main/java/com/helible/pilot/Kproto.kt deleted file mode 100644 index aec2803..0000000 --- a/app/src/main/java/com/helible/pilot/Kproto.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.helible.pilot - -// Todo: add checksum -// Todo: add arguments names - -data class KMessage( - val r1: UShort, - val r2: UShort, - val r3: UShort, - val emergStop: Boolean, - val alarm: Boolean, -) - -fun KMessage.toByteArray(): ByteArray { - return "$$r1;$r2;$r3;$emergStop;$alarm\r\n".encodeToByteArray() -} - -fun String.toKMessage(): KMessage { - // TODO: implement - return KMessage( - 0u, 0u, 0u, emergStop = false, alarm = false - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/MainActivity.kt b/app/src/main/java/com/helible/pilot/MainActivity.kt index 2e5e3f3..789bf76 100644 --- a/app/src/main/java/com/helible/pilot/MainActivity.kt +++ b/app/src/main/java/com/helible/pilot/MainActivity.kt @@ -17,6 +17,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel 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.deviceScreen.DeviceControlScreen import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList import com.helible.pilot.components.scannerScreen.ScannerScreen @@ -33,11 +34,7 @@ import com.helible.pilot.viewmodels.SavedPreferencesImpl class MainActivity : ComponentActivity() { - // TODO: device screen logic - // TODO: constrain text size - // TODO: add Bluetooth telemetry... // TODO: move text strings to resources - // TODO: review permissions logic private val preferences by lazy { SavedPreferencesImpl(getSharedPreferences(packageName, MODE_PRIVATE)) @@ -46,6 +43,7 @@ class MainActivity : ComponentActivity() { PreferencesViewModel(preferences) } + @ExperimentalStdlibApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { @@ -68,14 +66,6 @@ class MainActivity : ComponentActivity() { permissionLauncher.launch() } - - LaunchedEffect(key1 = bluetoothState) { - if (bluetoothState.isConnected) { - Toast.makeText(applicationContext, "Подключение завершено", Toast.LENGTH_SHORT) - .show() - } - } - val navController = rememberNavController() LaunchedEffect(key1 = bluetoothState.errorMessage) { @@ -168,7 +158,9 @@ class MainActivity : ComponentActivity() { } composable("imu_calibration/{title}") { backStackEntry -> - NotImplementedPage( + CalibrationPage( + deviceStatus = bluetoothState.deviceState?.status, + startCalibration = { bluetoothViewModel.startImuCalibration() }, title = backStackEntry.arguments?.getString("title") ?: "null", navigateBack = { navController.popBackStack() } ) diff --git a/app/src/main/java/com/helible/pilot/components/CalibrationPage.kt b/app/src/main/java/com/helible/pilot/components/CalibrationPage.kt new file mode 100644 index 0000000..4d8c5a5 --- /dev/null +++ b/app/src/main/java/com/helible/pilot/components/CalibrationPage.kt @@ -0,0 +1,84 @@ +package com.helible.pilot.components + +import android.widget.Spinner +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.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.helible.pilot.R +import com.helible.pilot.dataclasses.BluetoothUiState +import com.helible.pilot.dataclasses.ChangedDeviceStatus +import com.helible.pilot.dataclasses.DeviceState +import com.helible.pilot.dataclasses.DeviceStatus + +@Composable +fun CalibrationPage( + deviceStatus: DeviceStatus?, + title: String, + startCalibration: () -> Unit, + navigateBack: () -> Unit +) { + BlankPage(title = title, navigateBack = navigateBack) { + Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = LocalContext.current.getString(R.string.calibration_description), + color = MaterialTheme.colorScheme.onSecondaryContainer, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp) + ) + Button( + enabled = deviceStatus != DeviceStatus.IsImuCalibration, + onClick = startCalibration, + modifier = Modifier.padding(10.dp) + ) { + if(deviceStatus != DeviceStatus.IsImuCalibration) { + Icon( + painter = painterResource(id = R.drawable.tune), + contentDescription = null, + modifier = Modifier.padding(3.dp) + ) + Text( + text = "Начать калибровку" + ) + } else { + CircularProgressIndicator () + Text( + text = "Калибровка...", + modifier = Modifier.padding(5.dp) + ) + } + } + } + } +} + +@Preview +@Composable +fun CalibrationPagePreview() { + Surface { + CalibrationPage( + DeviceStatus.IsImuCalibration, + title = "Калибровка гироскопа и акселерометра", + startCalibration = {}, + navigateBack = {} + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceBadge.kt b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceBadge.kt index 2203d0e..a69299e 100644 --- a/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceBadge.kt +++ b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceBadge.kt @@ -19,6 +19,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -26,6 +27,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.helible.pilot.R import com.helible.pilot.dataclasses.BluetoothUiState +import com.helible.pilot.dataclasses.DeviceStatus import com.helible.pilot.viewmodels.AppPreferences @Composable @@ -65,7 +67,19 @@ fun DeviceBadge( fontWeight = FontWeight.Bold ) DeviceConnectionStatus(bluetoothUiState) - Text(text = "Заряд батареи: 79%") + if(bluetoothUiState.isConnected) { + val deviceStatus = bluetoothUiState.deviceState?.status + if (deviceStatus != null) { + Text(text = "Заряд батареи: ${bluetoothUiState.deviceState.batteryCharge}%") + if (deviceStatus == DeviceStatus.ChargeRequired) { + Text(text = "Аккумулятор разряжен", color = Color.Red) + } else { + Text(text = deviceStatus.description()) + } + } else { + Text(text = "Ожиданием рукопожатия...") + } + } } Box( contentAlignment = Alignment.CenterEnd, diff --git a/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceConnectionStatus.kt b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceConnectionStatus.kt index 1ce477f..08bca2e 100644 --- a/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceConnectionStatus.kt +++ b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceConnectionStatus.kt @@ -51,6 +51,8 @@ fun DeviceConnectionStatus(bluetoothState: BluetoothUiState) { .padding(2.dp) ) Text("Подключение...") + } else { + Text("Попытка подключения не удалась.") } } } diff --git a/app/src/main/java/com/helible/pilot/controllers/BluetoothController.kt b/app/src/main/java/com/helible/pilot/controllers/BluetoothController.kt index 9e5a628..9372543 100644 --- a/app/src/main/java/com/helible/pilot/controllers/BluetoothController.kt +++ b/app/src/main/java/com/helible/pilot/controllers/BluetoothController.kt @@ -15,8 +15,8 @@ import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import com.helible.pilot.viewmodels.BluetoothDataTransferService -import com.helible.pilot.KMessage import com.helible.pilot.dataclasses.BluetoothDeviceDomain +import com.helible.pilot.dataclasses.DeviceState 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: String) : ConnectionResult + data class TransferSucceded(val message: DeviceState) : ConnectionResult data class Error(val message: String) : ConnectionResult } @@ -61,6 +61,7 @@ interface BluetoothController { fun onDestroy() } +@ExperimentalStdlibApi class AndroidBluetoothController(private val context: Context) : BluetoothController { private val bluetoothManager by lazy { @@ -228,21 +229,26 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro try { socket.connect() emit(ConnectionResult.ConnectionEstablished) - BluetoothDataTransferService(socket).also { it -> + BluetoothDataTransferService(socket).also { dataTransferService = it emitAll( it.listenForIncomingMessages() - .map { ConnectionResult.TransferSucceded(it) } + .map { deviceState -> + ConnectionResult.TransferSucceded(deviceState) + } ) } } catch (e: IOException) { socket.close() currentClientSocket = null - Log.e("BluetoothController", e.toString()) + Log.e("BluetoothController", "I/O exception: e") emit(ConnectionResult.Error("Connection was interrupted")) } } - }.onCompletion { closeConnection() }.flowOn(Dispatchers.IO) + }.onCompletion { + Log.i("BluetoothController", "Connection closed on flow completion.") + closeConnection() + }.flowOn(Dispatchers.IO) } override suspend fun trySendMessage(message: ByteArray): Boolean { diff --git a/app/src/main/java/com/helible/pilot/dataclasses/BluetoothUiState.kt b/app/src/main/java/com/helible/pilot/dataclasses/BluetoothUiState.kt index 42d949c..3307db4 100644 --- a/app/src/main/java/com/helible/pilot/dataclasses/BluetoothUiState.kt +++ b/app/src/main/java/com/helible/pilot/dataclasses/BluetoothUiState.kt @@ -9,4 +9,5 @@ data class BluetoothUiState( val errorMessage: String? = null, val scannedBluetoothDevices: List = emptyList(), val pairedBluetoothDevices: List = emptyList(), + val deviceState: DeviceState? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/dataclasses/ChangedDeviceStatus.kt b/app/src/main/java/com/helible/pilot/dataclasses/ChangedDeviceStatus.kt new file mode 100644 index 0000000..b970fde --- /dev/null +++ b/app/src/main/java/com/helible/pilot/dataclasses/ChangedDeviceStatus.kt @@ -0,0 +1,6 @@ +package com.helible.pilot.dataclasses + + +data class ChangedDeviceStatus( + val status: DeviceStatus +) \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/dataclasses/DeviceState.kt b/app/src/main/java/com/helible/pilot/dataclasses/DeviceState.kt new file mode 100644 index 0000000..3f4fea3 --- /dev/null +++ b/app/src/main/java/com/helible/pilot/dataclasses/DeviceState.kt @@ -0,0 +1,15 @@ +package com.helible.pilot.dataclasses + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class DeviceState( + val status: DeviceStatus = DeviceStatus.ChargeRequired, + @Json(name = "charge") val batteryCharge: Int = 10, + val flightHeight: Float = 0f, + @Json(name = "y") val yaw: Float = 0f, + @Json(name = "p") val pitch: Float = 0f, + @Json(name = "r") val roll: Float = 0f, + @Json(name = "zIn") val zInertial: Float = 0f, +) \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/dataclasses/DeviceStatus.kt b/app/src/main/java/com/helible/pilot/dataclasses/DeviceStatus.kt new file mode 100644 index 0000000..aeaf932 --- /dev/null +++ b/app/src/main/java/com/helible/pilot/dataclasses/DeviceStatus.kt @@ -0,0 +1,21 @@ +package com.helible.pilot.dataclasses + +enum class DeviceStatus { + Idle, + IsPreparingForTakeoff, + IsFlying, + IsBoarding, + IsImuCalibration, + ChargeRequired; + + fun description(): String { + return when (this) { + Idle -> "Готово к работе" + IsPreparingForTakeoff -> "Подготовка к полёту" + IsFlying -> "В полёте" + IsBoarding -> "Посадка" + IsImuCalibration -> "Калибровка..." + ChargeRequired -> "Аккумулятор разряжен" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/dataclasses/DeviceStatusJsonAdapter.kt b/app/src/main/java/com/helible/pilot/dataclasses/DeviceStatusJsonAdapter.kt new file mode 100644 index 0000000..8da1a97 --- /dev/null +++ b/app/src/main/java/com/helible/pilot/dataclasses/DeviceStatusJsonAdapter.kt @@ -0,0 +1,22 @@ +package com.helible.pilot.dataclasses + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.ToJson + +class DeviceStatusJsonAdapter { + @FromJson + fun fromJson(deviceStatus: String): DeviceStatus { + try { + val index: UInt = deviceStatus.toUInt() + return DeviceStatus.values()[index.toInt()] + } catch (e: IndexOutOfBoundsException) { + throw JsonDataException("Impossible conversation from String to DeviceStatus") + } + } + + @ToJson + fun toJson(deviceStatus: DeviceStatus): String { + return DeviceStatus.values().indexOf(deviceStatus).toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/dataclasses/MessageTypes.kt b/app/src/main/java/com/helible/pilot/dataclasses/MessageTypes.kt deleted file mode 100644 index 58df9d3..0000000 --- a/app/src/main/java/com/helible/pilot/dataclasses/MessageTypes.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.helible.pilot.dataclasses - -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class RotorsSpeedMessage(val r1: Short, val r2: Short, val r3: Short) - -@JsonClass(generateAdapter = true) -data class EmergStopMessage(val emergStop: Boolean) - -@JsonClass(generateAdapter = true) -data class AlarmStateMessage(val isAlarmOn: Boolean) \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/viewmodels/BluetoothDataTransferService.kt b/app/src/main/java/com/helible/pilot/viewmodels/BluetoothDataTransferService.kt index 2bc7eb7..4e4322b 100644 --- a/app/src/main/java/com/helible/pilot/viewmodels/BluetoothDataTransferService.kt +++ b/app/src/main/java/com/helible/pilot/viewmodels/BluetoothDataTransferService.kt @@ -2,8 +2,12 @@ package com.helible.pilot.viewmodels import android.bluetooth.BluetoothSocket import android.util.Log -import com.helible.pilot.KMessage -import com.helible.pilot.toKMessage +import com.helible.pilot.dataclasses.DeviceState +import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter +import com.squareup.moshi.JsonEncodingException +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -14,25 +18,42 @@ import java.io.IOException class TransferFailedException : IOException("Reading incoming data failed") +@ExperimentalStdlibApi class BluetoothDataTransferService( private val socket: BluetoothSocket, ) { - fun listenForIncomingMessages(): Flow { + fun listenForIncomingMessages(): Flow { + val moshi = Moshi.Builder().add(DeviceStatusJsonAdapter()).add(KotlinJsonAdapterFactory()).build() + val deviceStateMessageAdapter = moshi.adapter() return flow { if (!socket.isConnected) return@flow - val buffer = ByteArray(128) + val buffer = ByteArray(512) while (true) { val byteCount: Int = try { socket.inputStream.read(buffer) } catch (e: IOException) { + Log.e("BluetoothController", "Failed to receive incoming data") throw TransferFailedException() } - val strData: String = buffer.decodeToString(endIndex = byteCount) - emit( - strData - ) - Log.i("BluetoothController", "Received: ${strData.dropLast(2)}") + + val messageData: String = buffer.decodeToString(endIndex = byteCount) + if (!messageData.endsWith("\n\r")) { + 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) } @@ -42,10 +63,11 @@ class BluetoothDataTransferService( try { socket.outputStream.write(bytes) } catch (e: IOException) { - e.printStackTrace() + Log.e("BluetoothController", "Failed to write message: $e") return@withContext false } true } } -} \ No newline at end of file +} + diff --git a/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModel.kt b/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModel.kt index aef50e7..9520476 100644 --- a/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModel.kt +++ b/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModel.kt @@ -1,18 +1,18 @@ package com.helible.pilot.viewmodels +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.helible.pilot.controllers.BluetoothController import com.helible.pilot.controllers.ConnectionResult -import com.helible.pilot.dataclasses.AlarmStateMessage import com.helible.pilot.dataclasses.BluetoothDevice import com.helible.pilot.dataclasses.BluetoothUiState -import com.helible.pilot.dataclasses.EmergStopMessage -import com.helible.pilot.dataclasses.RotorsSpeedMessage +import com.helible.pilot.dataclasses.ChangedDeviceStatus +import com.helible.pilot.dataclasses.DeviceStatus +import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter 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 @@ -25,6 +25,7 @@ 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, @@ -43,10 +44,9 @@ class BluetoothViewModel( pairedBluetoothDevices = pairedDevices ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), _state.value) - private val moshi: Moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() - private val rotorsStateMessegeAdapter = moshi.adapter(RotorsSpeedMessage::class.java) - private val alarmStateMessageAdapter = moshi.adapter(AlarmStateMessage::class.java) - private val emergStopMessageAdapter = moshi.adapter(EmergStopMessage::class.java) + private var deviceConnectionJob: Job? = null + private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).add(DeviceStatusJsonAdapter()).build() + private val newStatusMessageAdapter = moshi.adapter(ChangedDeviceStatus::class.java) init { bluetoothController.isConnected.onEach { isConnected -> @@ -94,9 +94,11 @@ class BluetoothViewModel( } is ConnectionResult.TransferSucceded -> { - _state.update { it.copy( - - ) } + _state.update { + it.copy( + deviceState = result.message + ) + } } is ConnectionResult.Error -> { @@ -111,19 +113,19 @@ class BluetoothViewModel( } } .catch { throwable -> + Log.e("BluetoothController", "Error occured while data transfer: ${throwable.message}") bluetoothController.closeConnection() _state.update { it.copy( isConnected = false, - isConnecting = false + isConnecting = false, + deviceState = null ) } } .launchIn(viewModelScope) } - private var deviceConnectionJob: Job? = null - fun connectToDevice(device: String) { - if (_state.value.isConnected and _state.value.isConnecting) { + if (_state.value.isConnected or _state.value.isConnecting) { return } _state.update { it.copy(isConnecting = true) } @@ -138,7 +140,8 @@ class BluetoothViewModel( _state.update { it.copy( isConnecting = false, - isConnected = false + isConnected = false, + deviceState = null ) } } @@ -170,4 +173,18 @@ class BluetoothViewModel( ) } } + + fun startImuCalibration() { + viewModelScope.launch { + val message = newStatusMessageAdapter.toJson( + ChangedDeviceStatus(DeviceStatus.IsImuCalibration) + ) + "\n\r" + val success = bluetoothController.trySendMessage( + message.toByteArray() + ) + if(!success) { + Log.e("BluetoothVM", "Failed to start IMU calibration: $message") + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModelFactory.kt b/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModelFactory.kt index 215845f..9d6e190 100644 --- a/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModelFactory.kt +++ b/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModelFactory.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.helible.pilot.controllers.AndroidBluetoothController +@ExperimentalStdlibApi @Suppress("UNCHECKED_CAST") class BluetoothViewModelFactory(private val context: Context) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2d01e9b..b710f46 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ Digital Pilot + Расположите устройство на ровной горизонтальной поверхности, чтобы сани вертолета полностью лежали на ней. Нажмите кнопку калибровки ниже и ждите её окончания, не создавая тряски. \ No newline at end of file