@@ -85,6 +85,7 @@ class BluetoothService {
|
||||
|
||||
/**
|
||||
* Initialize ELM327 with default commands
|
||||
* Based on standard ELM327 initialization sequence
|
||||
*/
|
||||
private suspend fun initializeElm327() {
|
||||
// Reset device
|
||||
@@ -99,13 +100,23 @@ class BluetoothService {
|
||||
sendCommand("ATL0")
|
||||
kotlinx.coroutines.delay(100)
|
||||
|
||||
// Set spaces off
|
||||
// Set spaces off (faster parsing)
|
||||
sendCommand("ATS0")
|
||||
kotlinx.coroutines.delay(100)
|
||||
|
||||
// Turn off headers (shorter responses)
|
||||
sendCommand("ATH0")
|
||||
kotlinx.coroutines.delay(100)
|
||||
|
||||
// Set adaptive timing to mode 2 (aggressive learning for faster responses)
|
||||
sendCommand("ATAT2")
|
||||
kotlinx.coroutines.delay(100)
|
||||
|
||||
// Set protocol to automatic
|
||||
sendCommand("ATSP0")
|
||||
kotlinx.coroutines.delay(100)
|
||||
|
||||
Log.d(TAG, "ELM327 initialized successfully")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,8 +128,15 @@ class BluetoothService {
|
||||
}
|
||||
|
||||
try {
|
||||
Log.d(TAG, "Preparing to send command: '$command'")
|
||||
Log.d(TAG, "Command type: ${command::class.java.simpleName}, length: ${command.length}")
|
||||
|
||||
val commandWithCR = "$command\r"
|
||||
outputStream?.write(commandWithCR.toByteArray())
|
||||
val bytes = commandWithCR.toByteArray()
|
||||
|
||||
Log.d(TAG, "Sending bytes: ${bytes.joinToString(" ") { "0x%02X".format(it) }}")
|
||||
|
||||
outputStream?.write(bytes)
|
||||
outputStream?.flush()
|
||||
|
||||
Log.d(TAG, "Sent: $command")
|
||||
@@ -130,7 +148,7 @@ class BluetoothService {
|
||||
Log.d(TAG, "Received: $response")
|
||||
Result.success(response)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Send command error", e)
|
||||
Log.e(TAG, "Send command error: ${e.message}", e)
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,54 @@ import com.manalejandro.odb2bluetooth.ui.components.WarningDialog
|
||||
import com.manalejandro.odb2bluetooth.ui.viewmodel.MainViewModel
|
||||
import com.manalejandro.odb2bluetooth.ui.viewmodel.VehicleViewModel
|
||||
|
||||
/**
|
||||
* Parse command string that may be in JSON format
|
||||
* Examples:
|
||||
* - "010C" -> "010C"
|
||||
* - "{\"22\": \"2203\"}" -> "2203"
|
||||
* - "{\"header\": \"7E0\", \"command\": \"2203\"}" -> "2203"
|
||||
*/
|
||||
private fun parseCommandString(commandStr: String?): String? {
|
||||
if (commandStr.isNullOrBlank()) return null
|
||||
|
||||
// If it doesn't start with {, it's already a plain command
|
||||
if (!commandStr.trim().startsWith("{")) {
|
||||
return commandStr.trim()
|
||||
}
|
||||
|
||||
// Try to parse as JSON-like format
|
||||
try {
|
||||
// Remove { } and split by comma
|
||||
val content = commandStr.trim().removeSurrounding("{", "}")
|
||||
val pairs = content.split(",")
|
||||
|
||||
for (pair in pairs) {
|
||||
val parts = pair.split(":")
|
||||
if (parts.size == 2) {
|
||||
val key = parts[0].trim().removeSurrounding("\"")
|
||||
val value = parts[1].trim().removeSurrounding("\"")
|
||||
|
||||
// Look for common command keys
|
||||
if (key.equals("command", ignoreCase = true) ||
|
||||
key.matches(Regex("\\d+"))) { // numeric key like "22"
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no specific key found, return the first value
|
||||
val firstPair = pairs.firstOrNull()?.split(":")
|
||||
if (firstPair?.size == 2) {
|
||||
return firstPair[1].trim().removeSurrounding("\"")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("VehicleSelection", "Error parsing command: $commandStr", e)
|
||||
}
|
||||
|
||||
// Fallback: return original if parsing fails
|
||||
return commandStr
|
||||
}
|
||||
|
||||
/**
|
||||
* Vehicle selection screen - Browse vehicles and their OBD signals
|
||||
*/
|
||||
@@ -40,6 +88,8 @@ fun VehicleSelectionScreen(
|
||||
var selectedSignal by remember { mutableStateOf<SignalDto?>(null) }
|
||||
|
||||
if (showWarning && selectedSignal != null) {
|
||||
val parsedCommand = parseCommandString(selectedSignal?.command)
|
||||
|
||||
WarningDialog(
|
||||
onDismiss = {
|
||||
showWarning = false
|
||||
@@ -47,7 +97,9 @@ fun VehicleSelectionScreen(
|
||||
},
|
||||
onAccept = {
|
||||
showWarning = false
|
||||
selectedSignal?.command?.let { command ->
|
||||
parsedCommand?.let { command ->
|
||||
android.util.Log.d("VehicleSelection", "Original: ${selectedSignal?.command}")
|
||||
android.util.Log.d("VehicleSelection", "Parsed command: $command")
|
||||
mainViewModel.sendCommand(command)
|
||||
}
|
||||
selectedSignal = null
|
||||
@@ -55,7 +107,8 @@ fun VehicleSelectionScreen(
|
||||
title = "Send OBD Command",
|
||||
message = "You are about to send this command:\n\n" +
|
||||
"Signal: ${selectedSignal?.signalName}\n" +
|
||||
"Command: ${selectedSignal?.command}\n" +
|
||||
"Raw: ${selectedSignal?.command}\n" +
|
||||
"Command to send: $parsedCommand\n" +
|
||||
"Description: ${selectedSignal?.description ?: "N/A"}\n\n" +
|
||||
"⚠️ Warning: Sending commands can potentially affect your vehicle. " +
|
||||
"Make sure you understand what this command does.\n\n" +
|
||||
@@ -320,6 +373,8 @@ private fun SignalCard(
|
||||
signal: SignalDto,
|
||||
onSend: () -> Unit
|
||||
) {
|
||||
val parsedCommand = parseCommandString(signal.command)
|
||||
|
||||
ElevatedCard(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
@@ -349,7 +404,7 @@ private fun SignalCard(
|
||||
}
|
||||
Button(
|
||||
onClick = onSend,
|
||||
enabled = signal.command != null
|
||||
enabled = parsedCommand != null
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Send,
|
||||
@@ -369,18 +424,26 @@ private fun SignalCard(
|
||||
|
||||
Divider()
|
||||
|
||||
Row(
|
||||
// Use FlowRow to wrap chips properly
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
signal.command?.let {
|
||||
InfoChip("Command", it)
|
||||
}
|
||||
signal.unit?.let {
|
||||
InfoChip("Unit", it)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
parsedCommand?.let {
|
||||
InfoChip("Command", it)
|
||||
}
|
||||
signal.unit?.let {
|
||||
InfoChip("Unit", it)
|
||||
}
|
||||
}
|
||||
signal.frequency?.let {
|
||||
InfoChip("Freq", "${it}Hz")
|
||||
Row {
|
||||
InfoChip("Frequency", "${it}Hz")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,16 +87,20 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Send a command to the OBD device
|
||||
*/
|
||||
fun sendCommand(command: String) {
|
||||
android.util.Log.d("OBD2_COMMAND", "Sending command: '$command' (type: ${command::class.simpleName})")
|
||||
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
bluetoothService.sendCommand(command).fold(
|
||||
onSuccess = { response ->
|
||||
android.util.Log.d("OBD2_COMMAND", "Command sent successfully. Response: $response")
|
||||
val parsed = ObdCommands.parseResponse(command, response) ?: response
|
||||
addCommandToHistory(command, response, parsed)
|
||||
_errorMessage.value = null
|
||||
},
|
||||
onFailure = { error ->
|
||||
android.util.Log.e("OBD2_COMMAND", "Command failed: ${error.message}")
|
||||
_errorMessage.value = "Command failed: ${error.message}"
|
||||
addCommandToHistory(command, "", "Error: ${error.message}")
|
||||
}
|
||||
|
||||
Referencia en una nueva incidencia
Block a user