diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts deleted file mode 100644 index 79e538c..0000000 --- a/android/app/build.gradle.kts +++ /dev/null @@ -1,48 +0,0 @@ -plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") -} - -android { - namespace = "ru.elmer.client" - compileSdk = 34 - - defaultConfig { - applicationId = "ru.elmer.client" - minSdk = 24 - targetSdk = 34 - versionCode = 1 - versionName = "0.1.0" - } - - buildTypes { - release { - isMinifyEnabled = true - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt")) - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" - } -} - -dependencies { - // AndrOBD library (forked ELM327 protocol) - implementation(project(":library")) - - // Minimal Android - implementation("androidx.core:core-ktx:1.12.0") - implementation("androidx.appcompat:appcompat:1.6.1") - - // HTTP - implementation("com.squareup.okhttp3:okhttp:4.12.0") - - // JSON - implementation("org.json:json:20231013") -} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 13fef69..0000000 --- a/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/java/ru/elmer/client/ElmForwardService.kt b/android/app/src/main/java/ru/elmer/client/ElmForwardService.kt deleted file mode 100644 index a2545f2..0000000 --- a/android/app/src/main/java/ru/elmer/client/ElmForwardService.kt +++ /dev/null @@ -1,218 +0,0 @@ -package ru.elmer.client - -import android.app.Service -import android.bluetooth.BluetoothAdapter -import android.bluetooth.BluetoothDevice -import android.bluetooth.BluetoothSocket -import android.content.Intent -import android.os.Handler -import android.os.IBinder -import android.os.Looper -import android.util.Log -import com.fr3ts0n.ecu.prot.obd.ElmProt -import okhttp3.* -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.RequestBody.Companion.toRequestBody -import org.json.JSONObject -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream -import java.util.* - -/** - * Фоновый сервис: Bluetooth SPP → ELM327 → HTTP-forward. - * Забираем сырые OBD-ответы из ElmProt и шлём на сервер. - */ -class ElmForwardService : Service() { - - private var btSocket: BluetoothSocket? = null - private var inputStream: InputStream? = null - private var outputStream: OutputStream? = null - private val elm = ElmProt() - private val okHttp = OkHttpClient.Builder() - .connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS) - .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS) - .build() - - private var serverUrl = "https://obdai.ru/api/v1/raw-obd" - private val handler = Handler(Looper.getMainLooper()) - private var running = false - private var readThread: Thread? = null - - companion object { - const val TAG = "ElmForward" - const val ACTION_CONNECT = "ru.elmer.client.CONNECT" - const val ACTION_DISCONNECT = "ru.elmer.client.DISCONNECT" - const val EXTRA_DEVICE_MAC = "device_mac" - const val EXTRA_SERVER_URL = "server_url" - const val BROADCAST_STATUS = "ru.elmer.client.STATUS" - } - - override fun onBind(intent: Intent?): IBinder? = null - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - intent?.let { - it.getStringExtra(EXTRA_SERVER_URL)?.let { url -> serverUrl = url } - } - - when (intent?.action) { - ACTION_CONNECT -> { - val mac = intent.getStringExtra(EXTRA_DEVICE_MAC) ?: return START_NOT_STICKY - connect(mac) - } - ACTION_DISCONNECT -> disconnect() - } - return START_STICKY - } - - // ── Bluetooth connection ────────────────────────── - - private fun connect(mac: String) { - broadcast("Подключение к $mac...") - - try { - val adapter = BluetoothAdapter.getDefaultAdapter() - val device: BluetoothDevice = adapter.getRemoteDevice(mac) - - // UUID для SPP (Serial Port Profile) - val uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") - btSocket = device.createRfcommSocketToServiceRecord(uuid) - adapter.cancelDiscovery() - btSocket?.connect() - - inputStream = btSocket?.inputStream - outputStream = btSocket?.outputStream - - // Настраиваем ELM протокол - elm.addTelegramWriter { buffer -> - outputStream?.write(String(buffer).toByteArray()) - } - - broadcast("Подключено к ELM327") - startReading() - initElm() - - } catch (e: Exception) { - Log.e(TAG, "Bluetooth error", e) - broadcast("Ошибка: ${e.message}") - } - } - - private fun initElm() { - // Отправляем AT-команды инициализации - sendRaw("ATZ\r") // сброс - Thread.sleep(1000) - sendRaw("ATE0\r") // эхо off - sendRaw("ATL0\r") // linefeed off - sendRaw("ATSP0\r") // авто-протокол - sendRaw("ATH1\r") // заголовки on - } - - // ── Reading loop ────────────────────────────────── - - private fun startReading() { - running = true - readThread = Thread { - val buffer = ByteArray(256) - val sb = StringBuilder() - - while (running) { - try { - val bytesRead = inputStream?.read(buffer) ?: -1 - if (bytesRead == -1) break - - for (i in 0 until bytesRead) { - val c = buffer[i].toInt().toChar() - if (c == '>' || c == '\r') { - if (sb.isNotEmpty()) { - val raw = sb.toString().trim() - if (raw.isNotEmpty()) { - elm.handleTelegram(raw.toCharArray()) - forwardToServer(raw) - } - sb.clear() - } - } else if (c != '\n') { - sb.append(c) - } - } - } catch (e: IOException) { - if (running) { - Log.e(TAG, "Read error", e) - broadcast("Соединение потеряно") - } - break - } - } - disconnect() - }.apply { - name = "ELM-Reader" - isDaemon = true - start() - } - - broadcast("Чтение данных...") - } - - // ── HTTP forward ────────────────────────────────── - - private fun forwardToServer(rawLine: String) { - try { - val json = JSONObject().apply { - put("raw", rawLine) - put("timestamp", System.currentTimeMillis()) - } - - val body = json.toString() - .toRequestBody("application/json; charset=utf-8".toMediaType()) - - val request = Request.Builder() - .url(serverUrl) - .post(body) - .build() - - okHttp.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - // Молча, сервер недоступен — не критично - } - override fun onResponse(call: Call, response: Response) { - response.close() - } - }) - } catch (e: Exception) { - Log.w(TAG, "Forward error", e) - } - } - - // ── Utils ───────────────────────────────────────── - - fun sendRaw(cmd: String) { - try { - outputStream?.write(cmd.toByteArray()) - outputStream?.flush() - } catch (e: Exception) { - Log.e(TAG, "Send error", e) - } - } - - private fun broadcast(msg: String) { - val intent = Intent(BROADCAST_STATUS).apply { - putExtra("message", msg) - } - sendBroadcast(intent) - } - - private fun disconnect() { - running = false - try { - btSocket?.close() - } catch (_: Exception) {} - broadcast("Отключено") - stopSelf() - } - - override fun onDestroy() { - disconnect() - super.onDestroy() - } -} diff --git a/android/app/src/main/java/ru/elmer/client/MainActivity.kt b/android/app/src/main/java/ru/elmer/client/MainActivity.kt deleted file mode 100644 index 42fa456..0000000 --- a/android/app/src/main/java/ru/elmer/client/MainActivity.kt +++ /dev/null @@ -1,108 +0,0 @@ -package ru.elmer.client - -import android.Manifest -import android.bluetooth.BluetoothAdapter -import android.bluetooth.BluetoothDevice -import android.content.* -import android.content.pm.PackageManager -import android.os.Build -import android.os.Bundle -import android.os.IBinder -import android.widget.Button -import android.widget.EditText -import android.widget.TextView -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat - -/** - * Тонкий клиент Elmer — одна кнопка. - * Подключается к ELM327 по Bluetooth, шлёт сырые OBD-ответы на сервер. - */ -class MainActivity : AppCompatActivity() { - - private lateinit var btnConnect: Button - private lateinit var tvStatus: TextView - private lateinit var etUrl: EditText - - private val btAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter() - private var elmDevice: BluetoothDevice? = null - - companion object { - const val REQUEST_BT_PERMISSIONS = 1 - const val REQUEST_ENABLE_BT = 2 - } - - private val statusReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - val msg = intent?.getStringExtra("message") ?: return - runOnUiThread { tvStatus.text = msg } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - btnConnect = findViewById(R.id.btn_connect) - tvStatus = findViewById(R.id.tv_status) - etUrl = findViewById(R.id.et_server_url) - - registerReceiver(statusReceiver, IntentFilter(ElmForwardService.BROADCAST_STATUS), - ContextCompat.RECEIVER_NOT_EXPORTED) - - btnConnect.setOnClickListener { startDiagnostics() } - } - - private fun startDiagnostics() { - if (btAdapter == null) { - tvStatus.text = "❌ Bluetooth не поддерживается" - return - } - - // Проверка разрешений (Android 12+) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) - != PackageManager.PERMISSION_GRANTED - ) { - ActivityCompat.requestPermissions( - this, - arrayOf(Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN), - REQUEST_BT_PERMISSIONS - ) - return - } - } - - if (!btAdapter.isEnabled) { - startActivityForResult(Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE_BT) - return - } - - // Ищем спаренный ELM327 (обычно название содержит "OBD" или "ELM") - val paired = btAdapter.bondedDevices - elmDevice = paired.find { d -> - d.name.uppercase().let { it.contains("OBD") || it.contains("ELM") } - } - - if (elmDevice == null) { - tvStatus.text = "❌ ELM327 не найден. Сопряги устройство в настройках Bluetooth." - return - } - - val intent = Intent(this, ElmForwardService::class.java).apply { - action = ElmForwardService.ACTION_CONNECT - putExtra(ElmForwardService.EXTRA_DEVICE_MAC, elmDevice!!.address) - putExtra(ElmForwardService.EXTRA_SERVER_URL, - etUrl.text.toString().ifBlank { "https://obdai.ru/api/v1/raw-obd" }) - } - ContextCompat.startForegroundService(this, intent) - tvStatus.text = "⏳ Подключение..." - } - - override fun onDestroy() { - unregisterReceiver(statusReceiver) - super.onDestroy() - } -} diff --git a/android/app/src/main/res/drawable/edit_bg.xml b/android/app/src/main/res/drawable/edit_bg.xml deleted file mode 100644 index e5ddf37..0000000 --- a/android/app/src/main/res/drawable/edit_bg.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 957d9fa..0000000 --- a/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - -