feat
This commit is contained in:
parent
33bd3eed44
commit
4e977f496b
|
@ -117,15 +117,15 @@ class MainActivity : ComponentActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查权限后尝试自动连接
|
// // 检查权限后尝试自动连接
|
||||||
lifecycleScope.launch {
|
// lifecycleScope.launch {
|
||||||
preferencesDataStore.getLastConnectedDevice().collect { address ->
|
// preferencesDataStore.getLastConnectedDevice().collect { address ->
|
||||||
if (address != null) {
|
// if (address != null) {
|
||||||
viewModel.connectToDeviceByAddress(address)
|
// viewModel.connectToDeviceByAddress(address)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
Esp32carTheme {
|
Esp32carTheme {
|
||||||
|
|
|
@ -11,6 +11,8 @@ object CarCommands {
|
||||||
const val CMD_GET_SPIFFS_STATUS = 0x11u
|
const val CMD_GET_SPIFFS_STATUS = 0x11u
|
||||||
const val CMD_GET_DISTANCE = 0x12u
|
const val CMD_GET_DISTANCE = 0x12u
|
||||||
|
|
||||||
|
const val CMD_SET_MODE = 0x30u
|
||||||
|
|
||||||
const val CMD_SET_NAME = 0xa1u
|
const val CMD_SET_NAME = 0xa1u
|
||||||
const val CMD_SET_PID = 0xa2u
|
const val CMD_SET_PID = 0xa2u
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package icu.fur93.esp32_car.const
|
||||||
|
|
||||||
|
object RunModeConsts {
|
||||||
|
const val MODE_MANUAL = 0x00u
|
||||||
|
const val MODE_TRACKING = 0x01u
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package icu.fur93.esp32_car.entity.car
|
||||||
|
|
||||||
data class CarState(
|
data class CarState(
|
||||||
val controlState: CarControlState = CarControlState(),
|
val controlState: CarControlState = CarControlState(),
|
||||||
|
val runModeState: RunModeState = RunModeState(),
|
||||||
val motorAState: MotorState = MotorState(),
|
val motorAState: MotorState = MotorState(),
|
||||||
val motorBState: MotorState = MotorState(),
|
val motorBState: MotorState = MotorState(),
|
||||||
val motorCState: MotorState = MotorState(),
|
val motorCState: MotorState = MotorState(),
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package icu.fur93.esp32_car.entity.car
|
||||||
|
|
||||||
|
import icu.fur93.esp32_car.const.RunModeConsts
|
||||||
|
|
||||||
|
data class RunModeState(
|
||||||
|
val runMode: UInt = RunModeConsts.MODE_MANUAL,
|
||||||
|
)
|
|
@ -3,5 +3,5 @@ package icu.fur93.esp32_car.entity.car
|
||||||
data class UltrasoundState(
|
data class UltrasoundState(
|
||||||
val enable: Boolean = false,
|
val enable: Boolean = false,
|
||||||
val servoAngle: UInt = 0u,
|
val servoAngle: UInt = 0u,
|
||||||
val distance: UInt = 0u
|
val distance: Float = 0f
|
||||||
)
|
)
|
|
@ -46,7 +46,7 @@ fun ControlPageStatusCard() {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ControlPageAutoControl() {
|
fun ControlPageAutoControl() {
|
||||||
val context = LocalContext.current
|
val navController = LocalNavigation.current
|
||||||
return CardButtonGroup(
|
return CardButtonGroup(
|
||||||
title = "自动控制",
|
title = "自动控制",
|
||||||
buttons = listOf(
|
buttons = listOf(
|
||||||
|
@ -54,7 +54,7 @@ fun ControlPageAutoControl() {
|
||||||
text = Route.ControlPathfinderMode.title,
|
text = Route.ControlPathfinderMode.title,
|
||||||
icon = ImageVector.vectorResource(R.drawable.outline_route_24),
|
icon = ImageVector.vectorResource(R.drawable.outline_route_24),
|
||||||
onClick = {
|
onClick = {
|
||||||
Toast.makeText(context, "暂未实现", Toast.LENGTH_SHORT).show()
|
navController.navigate(Route.ControlPathfinderMode.route)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,56 @@
|
||||||
package icu.fur93.esp32_car.page
|
package icu.fur93.esp32_car.page
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import icu.fur93.esp32_car.Route
|
||||||
|
import icu.fur93.esp32_car.ui.carditem.StatusCardInfo
|
||||||
|
import icu.fur93.esp32_car.ui.component.PageTitle
|
||||||
|
import icu.fur93.esp32_car.ui.theme.LayoutContentModifier
|
||||||
import icu.fur93.esp32_car.viewmodel.CarViewModel
|
import icu.fur93.esp32_car.viewmodel.CarViewModel
|
||||||
|
import icu.fur93.esp32_car.ui.carditem.StatusCardInfraredStatus
|
||||||
|
import icu.fur93.esp32_car.ui.carditem.StatusCardMotorStatus
|
||||||
|
import icu.fur93.esp32_car.ui.carditem.StatusCardPathfinderStatus
|
||||||
|
import icu.fur93.esp32_car.ui.component.StatusCard
|
||||||
|
import icu.fur93.esp32_car.const.RunModeConsts
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import icu.fur93.esp32_car.const.CarCommands
|
||||||
|
import icu.fur93.esp32_car.ui.carditem.StatusCardUltrasoundStatus
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ControlPathfinderModePage(viewModel: CarViewModel) {
|
fun ControlPathfinderModePage(viewModel: CarViewModel) {
|
||||||
|
|
||||||
|
Column(LayoutContentModifier) {
|
||||||
|
PageTitle(Route.ControlPathfinderMode.title)
|
||||||
|
ControlPathfinderModeStatusCard(viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ControlPathfinderModeStatusCard(viewModel: CarViewModel) {
|
||||||
|
val deviceInfo by viewModel.connectionInfoState.collectAsState()
|
||||||
|
val carState by viewModel.carState.collectAsState()
|
||||||
|
|
||||||
|
StatusCard(
|
||||||
|
cardItems = listOf(
|
||||||
|
StatusCardInfo(deviceInfo),
|
||||||
|
StatusCardMotorStatus(carState),
|
||||||
|
StatusCardInfraredStatus(carState.infraredState),
|
||||||
|
StatusCardUltrasoundStatus(carState.ultrasoundState),
|
||||||
|
StatusCardPathfinderStatus(carState.runModeState)
|
||||||
|
),
|
||||||
|
bottomControl = {
|
||||||
|
Button(onClick = {
|
||||||
|
if (carState.runModeState.runMode == RunModeConsts.MODE_TRACKING) {
|
||||||
|
viewModel.setRunMode(RunModeConsts.MODE_MANUAL.toInt())
|
||||||
|
} else {
|
||||||
|
viewModel.setRunMode(RunModeConsts.MODE_TRACKING.toInt())
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(if (carState.runModeState.runMode == RunModeConsts.MODE_TRACKING) "停止" else "开始")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -10,6 +10,9 @@ import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.FilledIconButton
|
import androidx.compose.material3.FilledIconButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -72,9 +75,36 @@ fun ControlSingleJoystickModeControlPanel(
|
||||||
viewModel: CarViewModel,
|
viewModel: CarViewModel,
|
||||||
onJoystickStateChange: (JoystickState) -> Unit
|
onJoystickStateChange: (JoystickState) -> Unit
|
||||||
) {
|
) {
|
||||||
|
// 添加Y轴限位模式的状态
|
||||||
|
var yAxisOnly by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Spacer(Modifier.height(24.dp))
|
Spacer(Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
// 添加开关控件
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Y 轴限位模式",
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
Switch(
|
||||||
|
checked = yAxisOnly,
|
||||||
|
onCheckedChange = { yAxisOnly = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 原有的控制面板布局
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
@ -85,7 +115,7 @@ fun ControlSingleJoystickModeControlPanel(
|
||||||
modifier = Modifier.pointerInteropFilter { event ->
|
modifier = Modifier.pointerInteropFilter { event ->
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
viewModel.sendXYR(0, 0, -50)
|
viewModel.rotateLeft(100)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
MotionEvent.ACTION_UP -> {
|
MotionEvent.ACTION_UP -> {
|
||||||
|
@ -101,7 +131,8 @@ fun ControlSingleJoystickModeControlPanel(
|
||||||
|
|
||||||
ControlJoystick(
|
ControlJoystick(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onJoystickStateChange = onJoystickStateChange
|
onJoystickStateChange = onJoystickStateChange,
|
||||||
|
yAxisOnly = yAxisOnly // 传递Y轴限位模式状态
|
||||||
)
|
)
|
||||||
|
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
|
@ -109,7 +140,7 @@ fun ControlSingleJoystickModeControlPanel(
|
||||||
modifier = Modifier.pointerInteropFilter { event ->
|
modifier = Modifier.pointerInteropFilter { event ->
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
viewModel.sendXYR(0, 0, 50)
|
viewModel.rotateRight(100)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
MotionEvent.ACTION_UP -> {
|
MotionEvent.ACTION_UP -> {
|
||||||
|
@ -124,11 +155,13 @@ fun ControlSingleJoystickModeControlPanel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ControlJoystick(
|
fun ControlJoystick(
|
||||||
viewModel: CarViewModel,
|
viewModel: CarViewModel,
|
||||||
onJoystickStateChange: (JoystickState) -> Unit
|
onJoystickStateChange: (JoystickState) -> Unit,
|
||||||
|
yAxisOnly: Boolean = false // 添加参数
|
||||||
) {
|
) {
|
||||||
// 记住上一次发送的状态和时间
|
// 记住上一次发送的状态和时间
|
||||||
val lastSentState = remember { mutableStateOf(JoystickState()) }
|
val lastSentState = remember { mutableStateOf(JoystickState()) }
|
||||||
|
@ -142,7 +175,8 @@ fun ControlJoystick(
|
||||||
|
|
||||||
Joystick(
|
Joystick(
|
||||||
size = 175.dp,
|
size = 175.dp,
|
||||||
knobScale = 0.25f
|
knobScale = 0.25f,
|
||||||
|
yAxisOnly = yAxisOnly, // 使用传入的参数
|
||||||
) { state ->
|
) { state ->
|
||||||
// 更新状态
|
// 更新状态
|
||||||
onJoystickStateChange(state)
|
onJoystickStateChange(state)
|
||||||
|
|
|
@ -21,6 +21,8 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import icu.fur93.esp32_car.BuildConfig
|
import icu.fur93.esp32_car.BuildConfig
|
||||||
import icu.fur93.esp32_car.R
|
import icu.fur93.esp32_car.R
|
||||||
import icu.fur93.esp32_car.Route
|
import icu.fur93.esp32_car.Route
|
||||||
|
@ -63,6 +65,8 @@ fun SettingsList(viewModel: CarViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
var debugMode by remember { mutableStateOf(DebugWindowManager.isShowing()) }
|
var debugMode by remember { mutableStateOf(DebugWindowManager.isShowing()) }
|
||||||
|
var showSensitivityDialog by remember { mutableStateOf(false) }
|
||||||
|
var showSpeedDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val onDebugModeChange = { enable: Boolean ->
|
val onDebugModeChange = { enable: Boolean ->
|
||||||
if (enable) {
|
if (enable) {
|
||||||
|
@ -94,15 +98,121 @@ fun SettingsList(viewModel: CarViewModel) {
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text("PID 参数") },
|
headlineContent = { Text("循迹灵敏度设置") },
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Icon(
|
Icon(
|
||||||
ImageVector.vectorResource(R.drawable.arrow_right_24),
|
ImageVector.vectorResource(R.drawable.arrow_right_24),
|
||||||
contentDescription = "PID 参数"
|
contentDescription = "循迹灵敏度设置"
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable { showSensitivityDialog = true }
|
||||||
|
)
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text("循迹速度设置") },
|
||||||
|
trailingContent = {
|
||||||
|
Icon(
|
||||||
|
ImageVector.vectorResource(R.drawable.arrow_right_24),
|
||||||
|
contentDescription = "循迹速度设置"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable { showSpeedDialog = true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循迹灵敏度设置对话框
|
||||||
|
if (showSensitivityDialog) {
|
||||||
|
var sensitivity by remember { mutableStateOf("3") }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showSensitivityDialog = false },
|
||||||
|
title = { Text("设置循迹灵敏度") },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text("请输入灵敏度值 (1-10):")
|
||||||
|
androidx.compose.material3.TextField(
|
||||||
|
value = sensitivity,
|
||||||
|
onValueChange = {
|
||||||
|
if (it.isEmpty() || (it.toIntOrNull() in 1..10)) {
|
||||||
|
sensitivity = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
sensitivity.toIntOrNull()?.let { value ->
|
||||||
|
viewModel.setPathfinderSensitivity(value)
|
||||||
|
}
|
||||||
|
showSensitivityDialog = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("确定")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showSensitivityDialog = false }) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 循迹速度设置对话框
|
||||||
|
if (showSpeedDialog) {
|
||||||
|
var baseSpeed by remember { mutableStateOf("50") }
|
||||||
|
var turnSpeed by remember { mutableStateOf("50") }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showSpeedDialog = false },
|
||||||
|
title = { Text("设置循迹速度") },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text("请输入基础速度 (0-255):")
|
||||||
|
androidx.compose.material3.TextField(
|
||||||
|
value = baseSpeed,
|
||||||
|
onValueChange = {
|
||||||
|
if (it.isEmpty() || (it.toIntOrNull() in 0..255)) {
|
||||||
|
baseSpeed = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||||
|
)
|
||||||
|
Text("请输入转弯速度 (0-255):")
|
||||||
|
androidx.compose.material3.TextField(
|
||||||
|
value = turnSpeed,
|
||||||
|
onValueChange = {
|
||||||
|
if (it.isEmpty() || (it.toIntOrNull() in 0..255)) {
|
||||||
|
turnSpeed = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
val base = baseSpeed.toIntOrNull()
|
||||||
|
val turn = turnSpeed.toIntOrNull()
|
||||||
|
if (base != null && turn != null) {
|
||||||
|
viewModel.setPathfinderSpeed(base, turn)
|
||||||
|
}
|
||||||
|
showSpeedDialog = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("确定")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showSpeedDialog = false }) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ import icu.fur93.esp32_car.entity.LogEntry
|
||||||
import icu.fur93.esp32_car.entity.car.CarState
|
import icu.fur93.esp32_car.entity.car.CarState
|
||||||
import icu.fur93.esp32_car.entity.car.InfraredState
|
import icu.fur93.esp32_car.entity.car.InfraredState
|
||||||
import icu.fur93.esp32_car.entity.car.MotorState
|
import icu.fur93.esp32_car.entity.car.MotorState
|
||||||
|
import icu.fur93.esp32_car.entity.car.RunModeState
|
||||||
|
import icu.fur93.esp32_car.entity.car.UltrasoundState
|
||||||
|
import icu.fur93.esp32_car.utils.Utils
|
||||||
|
|
||||||
// 蓝牙通信接口
|
// 蓝牙通信接口
|
||||||
interface BluetoothRepository {
|
interface BluetoothRepository {
|
||||||
|
@ -91,6 +94,48 @@ class BluetoothRepositoryImpl(
|
||||||
private val _deviceAddress = MutableStateFlow<String?>(null)
|
private val _deviceAddress = MutableStateFlow<String?>(null)
|
||||||
override val deviceAddress: StateFlow<String?> = _deviceAddress.asStateFlow()
|
override val deviceAddress: StateFlow<String?> = _deviceAddress.asStateFlow()
|
||||||
|
|
||||||
|
private var notificationRetryCount = 0
|
||||||
|
private val MAX_NOTIFICATION_RETRIES = 3
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private fun enableNotifications(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
||||||
|
scope.launch {
|
||||||
|
var success = false
|
||||||
|
var retryCount = 0
|
||||||
|
|
||||||
|
while (!success && retryCount < MAX_NOTIFICATION_RETRIES) {
|
||||||
|
success = try {
|
||||||
|
gatt.setCharacteristicNotification(characteristic, true)
|
||||||
|
|
||||||
|
// 等待一小段时间确保设置生效
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
// 写入描述符
|
||||||
|
characteristic.descriptors.forEach { descriptor ->
|
||||||
|
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||||
|
gatt.writeDescriptor(descriptor)
|
||||||
|
// 等待描述符写入完成
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("BluetoothRepository", "启用通知失败: ${e.message}")
|
||||||
|
delay(200) // 失败后等待一段时间再重试
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
retryCount++
|
||||||
|
Log.d("BluetoothRepository", "重试启用通知 #$retryCount")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
Log.e("BluetoothRepository", "无法启用通知,已达到最大重试次数")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val scanCallback = object : ScanCallback() {
|
private val scanCallback = object : ScanCallback() {
|
||||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||||
val bleDevice = BleDevice(
|
val bleDevice = BleDevice(
|
||||||
|
@ -213,33 +258,45 @@ class BluetoothRepositoryImpl(
|
||||||
|
|
||||||
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
|
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
|
||||||
super.onServicesDiscovered(gatt, status)
|
super.onServicesDiscovered(gatt, status)
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS && gatt != null) {
|
||||||
// 读取设备名称
|
// 读取设备名称
|
||||||
val genericService =
|
val genericService = gatt.getService(UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"))
|
||||||
gatt?.getService(UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"))
|
val deviceNameChar = genericService?.getCharacteristic(UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb"))
|
||||||
val deviceNameChar =
|
|
||||||
genericService?.getCharacteristic(UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb"))
|
|
||||||
if (deviceNameChar != null) {
|
if (deviceNameChar != null) {
|
||||||
gatt.readCharacteristic(deviceNameChar)
|
gatt.readCharacteristic(deviceNameChar)
|
||||||
}
|
}
|
||||||
|
|
||||||
gatt?.getService(UUID.fromString(serviceUUID))?.let { service ->
|
gatt.getService(UUID.fromString(serviceUUID))?.let { service ->
|
||||||
bluetoothGatt = gatt
|
bluetoothGatt = gatt
|
||||||
rxCharacteristic =
|
rxCharacteristic = service.getCharacteristic(UUID.fromString(rxCharUUID))
|
||||||
service.getCharacteristic(UUID.fromString(rxCharUUID))
|
|
||||||
|
|
||||||
// 注册通知监听器
|
// 使用新的通知启用方法
|
||||||
val txCharacteristic =
|
val txCharacteristic = service.getCharacteristic(UUID.fromString(txCharUUID))
|
||||||
service.getCharacteristic(UUID.fromString(txCharUUID))
|
if (txCharacteristic != null) {
|
||||||
gatt.setCharacteristicNotification(txCharacteristic, true)
|
enableNotifications(gatt, txCharacteristic)
|
||||||
txCharacteristic.descriptors.forEach { descriptor ->
|
|
||||||
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
|
||||||
gatt.writeDescriptor(descriptor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDescriptorWrite(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
descriptor: BluetoothGattDescriptor,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onDescriptorWrite(gatt, descriptor, status)
|
||||||
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
Log.e("BluetoothRepository", "描述符写入失败: $status")
|
||||||
|
// 如果写入失败,尝试重新启用通知
|
||||||
|
val characteristic = descriptor.characteristic
|
||||||
|
if (characteristic.uuid == UUID.fromString(txCharUUID)) {
|
||||||
|
enableNotifications(gatt, characteristic)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("BluetoothRepository", "描述符写入成功")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCharacteristicRead(
|
override fun onCharacteristicRead(
|
||||||
gatt: BluetoothGatt,
|
gatt: BluetoothGatt,
|
||||||
characteristic: BluetoothGattCharacteristic,
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
@ -260,6 +317,7 @@ class BluetoothRepositoryImpl(
|
||||||
value: ByteArray
|
value: ByteArray
|
||||||
) {
|
) {
|
||||||
super.onCharacteristicChanged(gatt, characteristic, value)
|
super.onCharacteristicChanged(gatt, characteristic, value)
|
||||||
|
Log.d("onCharacteristicChanged", "characteristic: ${characteristic.uuid}, value: ${value.joinToString(" ")}")
|
||||||
if (characteristic.uuid == UUID.fromString(txCharUUID)) {
|
if (characteristic.uuid == UUID.fromString(txCharUUID)) {
|
||||||
onReceivePacket(value)
|
onReceivePacket(value)
|
||||||
}
|
}
|
||||||
|
@ -274,46 +332,6 @@ class BluetoothRepositoryImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
fun connectToDeviceByAddress(
|
|
||||||
address: String,
|
|
||||||
callback: ((Boolean, BluetoothDevice) -> Unit)? = null
|
|
||||||
) {
|
|
||||||
val bluetoothManager =
|
|
||||||
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
|
||||||
val device = bluetoothManager.adapter.getRemoteDevice(address)
|
|
||||||
if (callback != null) {
|
|
||||||
connectToDevice(device) { isConnected, deviceDetail ->
|
|
||||||
callback(isConnected, deviceDetail ?: device)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
connectToDevice(device)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
private fun setupDeviceNameNotification(gatt: BluetoothGatt) {
|
|
||||||
val service = gatt.getService(UUID.fromString(deviceInfoServiceUUID))
|
|
||||||
val characteristic = service?.getCharacteristic(UUID.fromString(deviceNameCharUUID))
|
|
||||||
|
|
||||||
characteristic?.let { char ->
|
|
||||||
// 启用通知
|
|
||||||
gatt.setCharacteristicNotification(char, true)
|
|
||||||
|
|
||||||
// 获取 descriptor 并写入通知使能值
|
|
||||||
val descriptor =
|
|
||||||
char.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
|
|
||||||
descriptor?.let {
|
|
||||||
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
|
||||||
gatt.writeDescriptor(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同时也读取一次当前值
|
|
||||||
gatt.readCharacteristic(char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
override fun sendCommand(command: ByteArray) {
|
override fun sendCommand(command: ByteArray) {
|
||||||
rxCharacteristic?.let { characteristic ->
|
rxCharacteristic?.let { characteristic ->
|
||||||
|
@ -345,16 +363,17 @@ class BluetoothRepositoryImpl(
|
||||||
if (!isValidPacket(packet)) return
|
if (!isValidPacket(packet)) return
|
||||||
|
|
||||||
/**
|
/**
|
||||||
包体 `01 0E E0 电机A_IN_1_2 电机A_PWM 电机B_IN_1_2 电机B_PWM 电机C_IN_1_2 电机C_PWM 电机D_IN_1_2 电机D_PWM 红外引脚数量 红外数据 FE`
|
包体 `01 0F E0 运行模式 电机A_IN_1_2 电机A_PWM 电机B_IN_1_2 电机B_PWM 电机C_IN_1_2 电机C_PWM 电机D_IN_1_2 电机D_PWM 红外引脚数量 红外数据 FE`
|
||||||
|
|
||||||
| 数据 | 类型 | 表示 |
|
| 数据 | 类型 | 表示 |
|
||||||
| -------- | ---- | ---- |
|
| -------- | ---- | ---- |
|
||||||
|
| 运行模式 | 无符号整数 | 0x00 手动模式,0x01 巡线模式 |
|
||||||
| IN_1_2 | 无符号整数 | 按位表示 IN1 IN2 的值, 0000 00 IN2 IN1 |
|
| IN_1_2 | 无符号整数 | 按位表示 IN1 IN2 的值, 0000 00 IN2 IN1 |
|
||||||
| PWM | 无符号整数 | - |
|
| PWM | 无符号整数 | - |
|
||||||
| 红外引脚数量 | 无符号整数 | - |
|
| 红外引脚数量 | 无符号整数 | - |
|
||||||
| 红外数据 | 无符号整数 | 按位表示红外循迹模块数据, 0001 1111 |
|
| 红外数据 | 无符号整数 | 按位表示红外循迹模块数据, 0001 1111 |
|
||||||
|
|
||||||
示例 `01 0E E0 01 FF 02 FF 02 FF 01 FF 05 1F FE`
|
示例 `01 0F E0 00 01 FF 02 FF 02 FF 01 FF 05 1F FE`
|
||||||
**/
|
**/
|
||||||
|
|
||||||
when (packet[2].toUByte().toUInt()) {
|
when (packet[2].toUByte().toUInt()) {
|
||||||
|
@ -373,11 +392,22 @@ class BluetoothRepositoryImpl(
|
||||||
private fun updateStatus(packet: ByteArray) {
|
private fun updateStatus(packet: ByteArray) {
|
||||||
val data = packet.sliceArray(3 until packet[1].toUByte().toInt() - 1)
|
val data = packet.sliceArray(3 until packet[1].toUByte().toInt() - 1)
|
||||||
_carState.value = _carState.value.copy(
|
_carState.value = _carState.value.copy(
|
||||||
motorAState = createMotorState(data, 0),
|
runModeState = RunModeState(data[0].toUByte().toUInt()),
|
||||||
motorBState = createMotorState(data, 2),
|
motorAState = createMotorState(data, 1),
|
||||||
motorCState = createMotorState(data, 4),
|
motorBState = createMotorState(data, 3),
|
||||||
motorDState = createMotorState(data, 6),
|
motorCState = createMotorState(data, 5),
|
||||||
infraredState = createInfraredState(data[8].toUByte().toInt(), data[9].toUByte())
|
motorDState = createMotorState(data, 7),
|
||||||
|
infraredState = createInfraredState(data[9].toUByte().toInt(), data[10].toUByte()),
|
||||||
|
ultrasoundState = UltrasoundState(
|
||||||
|
true,
|
||||||
|
0u,
|
||||||
|
Utils.bytesToFloat(byteArrayOf(
|
||||||
|
data[11],
|
||||||
|
data[12],
|
||||||
|
data[13],
|
||||||
|
data[14]
|
||||||
|
))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,16 @@ import android.annotation.SuppressLint
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import icu.fur93.esp32_car.ui.component.CardItem
|
import icu.fur93.esp32_car.ui.component.CardItem
|
||||||
import icu.fur93.esp32_car.ui.component.StatusCardItemText
|
import icu.fur93.esp32_car.ui.component.StatusCardItemText
|
||||||
|
import icu.fur93.esp32_car.const.RunModeConsts
|
||||||
|
import icu.fur93.esp32_car.entity.car.RunModeState
|
||||||
|
|
||||||
@SuppressLint("ComposableNaming")
|
@SuppressLint("ComposableNaming")
|
||||||
@Composable
|
@Composable
|
||||||
fun StatusCardPathfinderStatus () : CardItem {
|
fun StatusCardPathfinderStatus (runModeState: RunModeState) : CardItem {
|
||||||
return CardItem(
|
return CardItem(
|
||||||
title = "循迹状态",
|
title = "循迹状态",
|
||||||
content = {
|
content = {
|
||||||
StatusCardItemText("进行中")
|
StatusCardItemText(if (runModeState.runMode == RunModeConsts.MODE_TRACKING) "进行中" else "未进行")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -22,6 +22,7 @@ fun Joystick(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
size: Dp = 200.dp,
|
size: Dp = 200.dp,
|
||||||
knobScale: Float = 0.4f,
|
knobScale: Float = 0.4f,
|
||||||
|
yAxisOnly: Boolean = false,
|
||||||
onJoystickMove: (JoystickState) -> Unit
|
onJoystickMove: (JoystickState) -> Unit
|
||||||
) {
|
) {
|
||||||
// 状态管理
|
// 状态管理
|
||||||
|
@ -46,14 +47,26 @@ fun Joystick(
|
||||||
onDragStart = { offset ->
|
onDragStart = { offset ->
|
||||||
isDragging = true
|
isDragging = true
|
||||||
centerPoint = Offset(sizePx / 2, sizePx / 2)
|
centerPoint = Offset(sizePx / 2, sizePx / 2)
|
||||||
updateStickPosition(offset, centerPoint, radius, innerRadius) { newPos ->
|
updateStickPosition(
|
||||||
|
offset,
|
||||||
|
centerPoint,
|
||||||
|
radius,
|
||||||
|
innerRadius,
|
||||||
|
yAxisOnly
|
||||||
|
) { newPos ->
|
||||||
stickPosition = newPos
|
stickPosition = newPos
|
||||||
val state = calculateJoystickState(centerPoint, newPos, radius)
|
val state = calculateJoystickState(centerPoint, newPos, radius)
|
||||||
onJoystickMove(state)
|
onJoystickMove(state)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDrag = { change, _ ->
|
onDrag = { change, _ ->
|
||||||
updateStickPosition(change.position, centerPoint, radius, innerRadius) { newPos ->
|
updateStickPosition(
|
||||||
|
change.position,
|
||||||
|
centerPoint,
|
||||||
|
radius,
|
||||||
|
innerRadius,
|
||||||
|
yAxisOnly
|
||||||
|
) { newPos ->
|
||||||
stickPosition = newPos
|
stickPosition = newPos
|
||||||
val state = calculateJoystickState(centerPoint, newPos, radius)
|
val state = calculateJoystickState(centerPoint, newPos, radius)
|
||||||
onJoystickMove(state)
|
onJoystickMove(state)
|
||||||
|
@ -93,25 +106,32 @@ private fun updateStickPosition(
|
||||||
centerPoint: Offset,
|
centerPoint: Offset,
|
||||||
maxRadius: Float,
|
maxRadius: Float,
|
||||||
knobRadius: Float,
|
knobRadius: Float,
|
||||||
|
yAxisOnly: Boolean,
|
||||||
onUpdate: (Offset) -> Unit
|
onUpdate: (Offset) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val deltaX = pointerPosition.x - centerPoint.x
|
||||||
|
val deltaY = pointerPosition.y - centerPoint.y
|
||||||
|
|
||||||
|
// 如果是 Y 轴限位模式,将 X 坐标固定在中心
|
||||||
|
val adjustedX = if (yAxisOnly) centerPoint.x else pointerPosition.x
|
||||||
|
val adjustedPosition = Offset(adjustedX, pointerPosition.y)
|
||||||
|
|
||||||
val distance = hypot(
|
val distance = hypot(
|
||||||
pointerPosition.x - centerPoint.x,
|
adjustedPosition.x - centerPoint.x,
|
||||||
pointerPosition.y - centerPoint.y
|
adjustedPosition.y - centerPoint.y
|
||||||
)
|
)
|
||||||
|
|
||||||
val newPosition = if (distance > maxRadius) {
|
val newPosition = if (distance > maxRadius) {
|
||||||
// 限制在最大范围内
|
|
||||||
val angle = atan2(
|
val angle = atan2(
|
||||||
pointerPosition.y - centerPoint.y,
|
adjustedPosition.y - centerPoint.y,
|
||||||
pointerPosition.x - centerPoint.x
|
adjustedPosition.x - centerPoint.x
|
||||||
)
|
)
|
||||||
Offset(
|
Offset(
|
||||||
centerPoint.x + (maxRadius * kotlin.math.cos(angle)),
|
centerPoint.x + (maxRadius * kotlin.math.cos(angle)),
|
||||||
centerPoint.y + (maxRadius * kotlin.math.sin(angle))
|
centerPoint.y + (maxRadius * kotlin.math.sin(angle))
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
pointerPosition
|
adjustedPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate(newPosition)
|
onUpdate(newPosition)
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package icu.fur93.esp32_car.utils
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* 将 Float 转换为字节数组
|
||||||
|
*/
|
||||||
|
fun floatToBytes(value: Float): ByteArray {
|
||||||
|
return value.toBits().let { bits ->
|
||||||
|
byteArrayOf(
|
||||||
|
(bits and 0xFF).toByte(),
|
||||||
|
((bits shr 8) and 0xFF).toByte(),
|
||||||
|
((bits shr 16) and 0xFF).toByte(),
|
||||||
|
((bits shr 24) and 0xFF).toByte()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字节数组转换为 Float
|
||||||
|
*/
|
||||||
|
fun bytesToFloat(bytes: ByteArray): Float {
|
||||||
|
require(bytes.size >= 4) { "字节数组长度必须大于等于4" }
|
||||||
|
|
||||||
|
return Float.fromBits(
|
||||||
|
(bytes[3].toInt() and 0xFF shl 24) or
|
||||||
|
(bytes[2].toInt() and 0xFF shl 16) or
|
||||||
|
(bytes[1].toInt() and 0xFF shl 8) or
|
||||||
|
(bytes[0].toInt() and 0xFF)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,10 @@ package icu.fur93.esp32_car.viewmodel
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.ui.platform.ComposeView
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import icu.fur93.esp32_car.const.CarCommands
|
import icu.fur93.esp32_car.const.CarCommands
|
||||||
|
import icu.fur93.esp32_car.const.RunModeConsts
|
||||||
import icu.fur93.esp32_car.entity.BleDevice
|
import icu.fur93.esp32_car.entity.BleDevice
|
||||||
import icu.fur93.esp32_car.entity.LogEntry
|
import icu.fur93.esp32_car.entity.LogEntry
|
||||||
import icu.fur93.esp32_car.entity.car.CarState
|
import icu.fur93.esp32_car.entity.car.CarState
|
||||||
|
@ -20,7 +19,6 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import android.view.WindowManager
|
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,10 +42,28 @@ class CarControlUseCase(
|
||||||
buildCommand(CarCommands.CMD_MOTOR_STEER_CONTROL, 0x01, speed)
|
buildCommand(CarCommands.CMD_MOTOR_STEER_CONTROL, 0x01, speed)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun rotateLeft(speed: Int = 255) = repository.sendCommand(
|
||||||
|
buildCommand(CarCommands.CMD_MOTOR_ROTATE_CONTROL, 0x01, speed)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun rotateRight(speed: Int = 255) = repository.sendCommand(
|
||||||
|
buildCommand(CarCommands.CMD_MOTOR_ROTATE_CONTROL, 0x00, speed)
|
||||||
|
)
|
||||||
|
|
||||||
fun stop() = repository.sendCommand(
|
fun stop() = repository.sendCommand(
|
||||||
buildCommand(CarCommands.CMD_MOTOR_MOVE_CONTROL, 0x00, 0x00)
|
buildCommand(CarCommands.CMD_MOTOR_MOVE_CONTROL, 0x00, 0x00)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun setRunMode(mode: Int) = repository.sendCommand(
|
||||||
|
byteArrayOf(
|
||||||
|
CarCommands.PACKET_T_HEAD.toByte(),
|
||||||
|
0x05,
|
||||||
|
CarCommands.CMD_SET_MODE.toByte(),
|
||||||
|
mode.toByte(),
|
||||||
|
CarCommands.PACKET_T_TAIL.toByte()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
fun sendXYR(x: Int, y: Int, r: Int) = repository.sendCommand(
|
fun sendXYR(x: Int, y: Int, r: Int) = repository.sendCommand(
|
||||||
byteArrayOf(
|
byteArrayOf(
|
||||||
CarCommands.PACKET_T_HEAD.toByte(),
|
CarCommands.PACKET_T_HEAD.toByte(),
|
||||||
|
@ -60,6 +76,27 @@ class CarControlUseCase(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun setPathfinderSpeed(baseSpeed: Int, turnSpeed: Int) = repository.sendCommand(
|
||||||
|
byteArrayOf(
|
||||||
|
CarCommands.PACKET_T_HEAD.toByte(),
|
||||||
|
0x06,
|
||||||
|
0xA3.toByte(),
|
||||||
|
baseSpeed.toByte(),
|
||||||
|
turnSpeed.toByte(),
|
||||||
|
CarCommands.PACKET_T_TAIL.toByte()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun setPathfinderSensitivity(sensitivity: Int) = repository.sendCommand(
|
||||||
|
byteArrayOf(
|
||||||
|
CarCommands.PACKET_T_HEAD.toByte(),
|
||||||
|
0x05,
|
||||||
|
0xA4.toByte(),
|
||||||
|
sensitivity.toByte(),
|
||||||
|
CarCommands.PACKET_T_TAIL.toByte()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
private fun buildCommand(cmd: UInt, param1: Int, param2: Int) = byteArrayOf(
|
private fun buildCommand(cmd: UInt, param1: Int, param2: Int) = byteArrayOf(
|
||||||
CarCommands.PACKET_T_HEAD.toByte(),
|
CarCommands.PACKET_T_HEAD.toByte(),
|
||||||
0x06,
|
0x06,
|
||||||
|
@ -88,11 +125,6 @@ class CarViewModel(
|
||||||
(repository as BluetoothRepositoryImpl).connectToDevice(device)
|
(repository as BluetoothRepositoryImpl).connectToDevice(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
fun connectToDeviceByAddress(address: String) {
|
|
||||||
(repository as BluetoothRepositoryImpl).connectToDeviceByAddress(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
val carState: StateFlow<CarState> = repository.observeCarState()
|
val carState: StateFlow<CarState> = repository.observeCarState()
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), CarState())
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), CarState())
|
||||||
|
|
||||||
|
@ -157,6 +189,15 @@ class CarViewModel(
|
||||||
fun turnLeft(speed: Int = 255) = carControlUseCase.turnLeft(speed)
|
fun turnLeft(speed: Int = 255) = carControlUseCase.turnLeft(speed)
|
||||||
fun turnRight(speed: Int = 255) = carControlUseCase.turnRight(speed)
|
fun turnRight(speed: Int = 255) = carControlUseCase.turnRight(speed)
|
||||||
fun stop() = carControlUseCase.stop()
|
fun stop() = carControlUseCase.stop()
|
||||||
|
fun rotateLeft(speed: Int = 255) = carControlUseCase.rotateLeft(speed)
|
||||||
|
fun rotateRight(speed: Int = 255) = carControlUseCase.rotateRight(speed)
|
||||||
|
fun setRunMode(mode: Int) = carControlUseCase.setRunMode(mode)
|
||||||
fun sendXYR(x: Int, y: Int, r: Int) = carControlUseCase.sendXYR(x, y, r)
|
fun sendXYR(x: Int, y: Int, r: Int) = carControlUseCase.sendXYR(x, y, r)
|
||||||
fun clearLogs() = repository.clearLogs()
|
fun clearLogs() = repository.clearLogs()
|
||||||
|
|
||||||
|
fun setPathfinderSpeed(baseSpeed: Int, turnSpeed: Int) =
|
||||||
|
carControlUseCase.setPathfinderSpeed(baseSpeed, turnSpeed)
|
||||||
|
|
||||||
|
fun setPathfinderSensitivity(sensitivity: Int) =
|
||||||
|
carControlUseCase.setPathfinderSensitivity(sensitivity)
|
||||||
}
|
}
|
Loading…
Reference in New Issue