Add Android BLE mesh client and project website

This commit is contained in:
dom4k
2026-03-16 19:04:20 +00:00
parent 2ad5e03cb7
commit c833fd467d
27 changed files with 1539 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
package com.schoolmesh.messenger
import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.schoolmesh.messenger.mesh.MeshForegroundService
import com.schoolmesh.messenger.mesh.MeshServiceContract
class MainActivity : AppCompatActivity() {
private lateinit var statusText: TextView
private lateinit var peersText: TextView
private lateinit var logsText: TextView
private val peers = linkedSetOf<String>()
private val logs = ArrayDeque<String>()
private val meshEventReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != MeshServiceContract.ACTION_EVENT) return
val eventType = intent.getStringExtra(MeshServiceContract.EXTRA_EVENT_TYPE) ?: return
val value = intent.getStringExtra(MeshServiceContract.EXTRA_EVENT_VALUE) ?: return
when (eventType) {
MeshServiceContract.EVENT_STATUS -> updateStatus(value)
MeshServiceContract.EVENT_PEER -> addPeer(value)
MeshServiceContract.EVENT_LOG -> appendLog(value)
}
}
}
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { result ->
val allGranted = result.values.all { it }
if (allGranted) {
startMesh()
} else {
updateStatus("Нет BLE-разрешений")
appendLog("Permissions denied by user")
Toast.makeText(this, "Разрешения отклонены", Toast.LENGTH_SHORT).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
statusText = findViewById(R.id.statusText)
peersText = findViewById(R.id.peersText)
logsText = findViewById(R.id.logsText)
findViewById<Button>(R.id.btnStartMesh).setOnClickListener {
ensurePermissionsAndStart()
}
findViewById<Button>(R.id.btnStopMesh).setOnClickListener {
MeshForegroundService.stop(this)
updateStatus("Mesh остановлен")
appendLog("Mesh service stop requested")
}
renderPeers()
renderLogs()
}
override fun onStart() {
super.onStart()
registerMeshReceiver()
}
override fun onStop() {
unregisterReceiver(meshEventReceiver)
super.onStop()
}
private fun registerMeshReceiver() {
val filter = IntentFilter(MeshServiceContract.ACTION_EVENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(meshEventReceiver, filter, RECEIVER_NOT_EXPORTED)
} else {
@Suppress("DEPRECATION")
registerReceiver(meshEventReceiver, filter)
}
}
private fun ensurePermissionsAndStart() {
val missing = requiredPermissions().filter { permission ->
ContextCompat.checkSelfPermission(this, permission) != android.content.pm.PackageManager.PERMISSION_GRANTED
}
if (missing.isEmpty()) {
startMesh()
} else {
permissionLauncher.launch(missing.toTypedArray())
}
}
private fun requiredPermissions(): List<String> {
val permissions = mutableListOf<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissions += Manifest.permission.BLUETOOTH_SCAN
permissions += Manifest.permission.BLUETOOTH_CONNECT
permissions += Manifest.permission.BLUETOOTH_ADVERTISE
} else {
permissions += Manifest.permission.ACCESS_FINE_LOCATION
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions += Manifest.permission.POST_NOTIFICATIONS
}
return permissions
}
private fun startMesh() {
MeshForegroundService.start(this)
updateStatus("Запуск foreground service")
appendLog("Mesh service start requested")
Toast.makeText(this, "Mesh запускается", Toast.LENGTH_SHORT).show()
}
private fun updateStatus(text: String) {
statusText.text = text
}
private fun addPeer(address: String) {
if (peers.add(address)) {
renderPeers()
}
}
private fun appendLog(message: String) {
if (logs.size >= MAX_LOG_ENTRIES) {
logs.removeFirst()
}
logs.addLast(message)
renderLogs()
}
private fun renderPeers() {
peersText.text = if (peers.isEmpty()) {
"Узлы не найдены"
} else {
peers.joinToString(separator = "\n")
}
}
private fun renderLogs() {
logsText.text = if (logs.isEmpty()) {
"Лог пуст"
} else {
logs.joinToString(separator = "\n")
}
}
companion object {
private const val MAX_LOG_ENTRIES = 20
}
}