@@ -85,6 +85,7 @@ class BluetoothService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize ELM327 with default commands
|
* Initialize ELM327 with default commands
|
||||||
|
* Based on standard ELM327 initialization sequence
|
||||||
*/
|
*/
|
||||||
private suspend fun initializeElm327() {
|
private suspend fun initializeElm327() {
|
||||||
// Reset device
|
// Reset device
|
||||||
@@ -99,13 +100,23 @@ class BluetoothService {
|
|||||||
sendCommand("ATL0")
|
sendCommand("ATL0")
|
||||||
kotlinx.coroutines.delay(100)
|
kotlinx.coroutines.delay(100)
|
||||||
|
|
||||||
// Set spaces off
|
// Set spaces off (faster parsing)
|
||||||
sendCommand("ATS0")
|
sendCommand("ATS0")
|
||||||
kotlinx.coroutines.delay(100)
|
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
|
// Set protocol to automatic
|
||||||
sendCommand("ATSP0")
|
sendCommand("ATSP0")
|
||||||
kotlinx.coroutines.delay(100)
|
kotlinx.coroutines.delay(100)
|
||||||
|
|
||||||
|
Log.d(TAG, "ELM327 initialized successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,8 +128,15 @@ class BluetoothService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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"
|
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()
|
outputStream?.flush()
|
||||||
|
|
||||||
Log.d(TAG, "Sent: $command")
|
Log.d(TAG, "Sent: $command")
|
||||||
@@ -130,7 +148,7 @@ class BluetoothService {
|
|||||||
Log.d(TAG, "Received: $response")
|
Log.d(TAG, "Received: $response")
|
||||||
Result.success(response)
|
Result.success(response)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Send command error", e)
|
Log.e(TAG, "Send command error: ${e.message}", e)
|
||||||
Result.failure(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.MainViewModel
|
||||||
import com.manalejandro.odb2bluetooth.ui.viewmodel.VehicleViewModel
|
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
|
* Vehicle selection screen - Browse vehicles and their OBD signals
|
||||||
*/
|
*/
|
||||||
@@ -40,6 +88,8 @@ fun VehicleSelectionScreen(
|
|||||||
var selectedSignal by remember { mutableStateOf<SignalDto?>(null) }
|
var selectedSignal by remember { mutableStateOf<SignalDto?>(null) }
|
||||||
|
|
||||||
if (showWarning && selectedSignal != null) {
|
if (showWarning && selectedSignal != null) {
|
||||||
|
val parsedCommand = parseCommandString(selectedSignal?.command)
|
||||||
|
|
||||||
WarningDialog(
|
WarningDialog(
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
showWarning = false
|
showWarning = false
|
||||||
@@ -47,7 +97,9 @@ fun VehicleSelectionScreen(
|
|||||||
},
|
},
|
||||||
onAccept = {
|
onAccept = {
|
||||||
showWarning = false
|
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)
|
mainViewModel.sendCommand(command)
|
||||||
}
|
}
|
||||||
selectedSignal = null
|
selectedSignal = null
|
||||||
@@ -55,7 +107,8 @@ fun VehicleSelectionScreen(
|
|||||||
title = "Send OBD Command",
|
title = "Send OBD Command",
|
||||||
message = "You are about to send this command:\n\n" +
|
message = "You are about to send this command:\n\n" +
|
||||||
"Signal: ${selectedSignal?.signalName}\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" +
|
"Description: ${selectedSignal?.description ?: "N/A"}\n\n" +
|
||||||
"⚠️ Warning: Sending commands can potentially affect your vehicle. " +
|
"⚠️ Warning: Sending commands can potentially affect your vehicle. " +
|
||||||
"Make sure you understand what this command does.\n\n" +
|
"Make sure you understand what this command does.\n\n" +
|
||||||
@@ -320,6 +373,8 @@ private fun SignalCard(
|
|||||||
signal: SignalDto,
|
signal: SignalDto,
|
||||||
onSend: () -> Unit
|
onSend: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val parsedCommand = parseCommandString(signal.command)
|
||||||
|
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
@@ -349,7 +404,7 @@ private fun SignalCard(
|
|||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = onSend,
|
onClick = onSend,
|
||||||
enabled = signal.command != null
|
enabled = parsedCommand != null
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Send,
|
imageVector = Icons.Default.Send,
|
||||||
@@ -369,18 +424,26 @@ private fun SignalCard(
|
|||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
Row(
|
// Use FlowRow to wrap chips properly
|
||||||
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
signal.command?.let {
|
Row(
|
||||||
InfoChip("Command", it)
|
modifier = Modifier.fillMaxWidth(),
|
||||||
}
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
signal.unit?.let {
|
) {
|
||||||
InfoChip("Unit", it)
|
parsedCommand?.let {
|
||||||
|
InfoChip("Command", it)
|
||||||
|
}
|
||||||
|
signal.unit?.let {
|
||||||
|
InfoChip("Unit", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
signal.frequency?.let {
|
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
|
* Send a command to the OBD device
|
||||||
*/
|
*/
|
||||||
fun sendCommand(command: String) {
|
fun sendCommand(command: String) {
|
||||||
|
android.util.Log.d("OBD2_COMMAND", "Sending command: '$command' (type: ${command::class.simpleName})")
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isLoading.value = true
|
_isLoading.value = true
|
||||||
|
|
||||||
bluetoothService.sendCommand(command).fold(
|
bluetoothService.sendCommand(command).fold(
|
||||||
onSuccess = { response ->
|
onSuccess = { response ->
|
||||||
|
android.util.Log.d("OBD2_COMMAND", "Command sent successfully. Response: $response")
|
||||||
val parsed = ObdCommands.parseResponse(command, response) ?: response
|
val parsed = ObdCommands.parseResponse(command, response) ?: response
|
||||||
addCommandToHistory(command, response, parsed)
|
addCommandToHistory(command, response, parsed)
|
||||||
_errorMessage.value = null
|
_errorMessage.value = null
|
||||||
},
|
},
|
||||||
onFailure = { error ->
|
onFailure = { error ->
|
||||||
|
android.util.Log.e("OBD2_COMMAND", "Command failed: ${error.message}")
|
||||||
_errorMessage.value = "Command failed: ${error.message}"
|
_errorMessage.value = "Command failed: ${error.message}"
|
||||||
addCommandToHistory(command, "", "Error: ${error.message}")
|
addCommandToHistory(command, "", "Error: ${error.message}")
|
||||||
}
|
}
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user