kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
begin making new bt scan gui
rodzic
4e6d1be954
commit
bd65bfee0a
5
TODO.md
5
TODO.md
|
@ -1,7 +1,8 @@
|
|||
# High priority
|
||||
MVP features required for first public alpha
|
||||
|
||||
* start bt receive on boot
|
||||
* test bt boot behavior
|
||||
* fix BT device scanning - make a setup screen
|
||||
* when a text arrives, move that node info card to the bottom on the window - put the text to the left of the card. with a small arrow/distance/shortname
|
||||
* let the user type texts somewhere
|
||||
* include a background behind our cloud graphics, so redraws work properly
|
||||
|
@ -13,7 +14,6 @@ MVP features required for first public alpha
|
|||
* make nodeinfo card not look like ass
|
||||
* at connect we might receive messages before finished downloading the nodeinfo. In that case, process those messages later
|
||||
* connect to bluetooth device automatically using minimum power - start looking at phone boot
|
||||
* fix BT device scanning
|
||||
* call crashlytics from exceptionReporter!!! currently not logging failures caught there
|
||||
* test with oldest compatible android in emulator (see below for testing with hardware)
|
||||
* make playstore entry, first public alpha
|
||||
|
@ -94,3 +94,4 @@ Don't leave device discoverable. Don't let unpaired users do things with device
|
|||
* when notified phone should automatically download messages
|
||||
* use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state
|
||||
* all chat in the app defaults to group chat
|
||||
* start bt receive on boot
|
||||
|
|
|
@ -172,7 +172,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
/* Do this better FIXME */
|
||||
val usetbeam = false
|
||||
val address = if (usetbeam) "B4:E6:2D:EA:32:B7" else "24:6F:28:96:C9:2A"
|
||||
RadioInterfaceService.setBondedDeviceAddress(this, address)
|
||||
RadioInterfaceService.setBondedDeviceAddress(this, null)
|
||||
|
||||
requestPermission()
|
||||
}
|
||||
|
|
|
@ -97,7 +97,9 @@ class RadioInterfaceService : Service(), Logging {
|
|||
*/
|
||||
const val RADIO_CONNECTED_ACTION = "$prefix.CONNECT_CHANGED"
|
||||
|
||||
private val BTM_SERVICE_UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
|
||||
/// this service UUID is publically visible for scanning
|
||||
val BTM_SERVICE_UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
|
||||
|
||||
private val BTM_FROMRADIO_CHARACTER =
|
||||
UUID.fromString("8ba2bcc2-ee02-4a55-a531-c525c5e454d5")
|
||||
private val BTM_TORADIO_CHARACTER =
|
||||
|
@ -131,11 +133,19 @@ class RadioInterfaceService : Service(), Logging {
|
|||
private fun getPrefs(context: Context) =
|
||||
context.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
|
||||
|
||||
/// Return the device we are configured to use, or null for none
|
||||
fun getBondedDeviceAddress(context: Context) = getPrefs(context).getString("devAddr", null)
|
||||
private const val DEVADDR_KEY = "devAddr"
|
||||
|
||||
fun setBondedDeviceAddress(context: Context, addr: String) =
|
||||
getPrefs(context).edit(commit = true) { putString("devAddr", addr) }
|
||||
/// Return the device we are configured to use, or null for none
|
||||
fun getBondedDeviceAddress(context: Context) =
|
||||
getPrefs(context).getString(DEVADDR_KEY, null)
|
||||
|
||||
fun setBondedDeviceAddress(context: Context, addr: String?) =
|
||||
getPrefs(context).edit(commit = true) {
|
||||
if (addr == null)
|
||||
this.remove(DEVADDR_KEY)
|
||||
else
|
||||
putString(DEVADDR_KEY, addr)
|
||||
}
|
||||
}
|
||||
|
||||
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.compose.Composable
|
||||
import androidx.compose.Model
|
||||
import androidx.ui.core.Text
|
||||
import androidx.ui.layout.Column
|
||||
import androidx.ui.layout.Row
|
||||
import androidx.ui.tooling.preview.Preview
|
||||
|
||||
@Model
|
||||
data class BTScanEntry(val name: String, val macAddress: String, var selected: Boolean)
|
||||
|
||||
@Composable
|
||||
fun BTScanCard(node: BTScanEntry) {
|
||||
// Text("Node: ${it.user?.longName}")
|
||||
Row {
|
||||
Text(node.name)
|
||||
|
||||
Text(node.selected.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun btScanPreview() {
|
||||
Column {
|
||||
BTScanCard(BTScanEntry("Meshtastic_ab12", "xx", true))
|
||||
BTScanCard(BTScanEntry("Meshtastic_32ac", "xx", false))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.le.ScanCallback
|
||||
import android.bluetooth.le.ScanFilter
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.bluetooth.le.ScanSettings
|
||||
import android.os.ParcelUuid
|
||||
import androidx.compose.Composable
|
||||
import androidx.compose.Context
|
||||
import androidx.compose.ambient
|
||||
import androidx.compose.onActive
|
||||
import androidx.ui.core.ContextAmbient
|
||||
import androidx.ui.core.Text
|
||||
import androidx.ui.layout.Column
|
||||
import androidx.ui.tooling.preview.Preview
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.service.RadioInterfaceService
|
||||
|
||||
object BTLog : Logging
|
||||
|
||||
@Composable
|
||||
fun BTScanScreen() {
|
||||
val context = ambient(ContextAmbient)
|
||||
|
||||
/// Note: may be null on platforms without a bluetooth driver (ie. the emulator)
|
||||
val bluetoothAdapter =
|
||||
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
|
||||
|
||||
onActive {
|
||||
|
||||
if (bluetoothAdapter == null)
|
||||
BTLog.warn("No bluetooth adapter. Running under emulation?")
|
||||
else {
|
||||
val scanner = bluetoothAdapter.bluetoothLeScanner
|
||||
|
||||
val scanCallback = object : ScanCallback() {
|
||||
override fun onScanFailed(errorCode: Int) {
|
||||
TODO() // FIXME, update gui with message about this
|
||||
}
|
||||
|
||||
// For each device that appears in our scan, ask for its GATT, when the gatt arrives,
|
||||
// check if it is an eligable device and store it in our list of candidates
|
||||
// if that device later disconnects remove it as a candidate
|
||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||
|
||||
BTLog.info("onScanResult ${result.device.address}")
|
||||
|
||||
// We don't need any more results now
|
||||
// scanner.stopScan(this)
|
||||
}
|
||||
}
|
||||
|
||||
BTLog.debug("starting scan")
|
||||
|
||||
// filter and only accept devices that have a sw update service
|
||||
val filter =
|
||||
ScanFilter.Builder()
|
||||
.setServiceUuid(ParcelUuid(RadioInterfaceService.BTM_SERVICE_UUID))
|
||||
.build()
|
||||
|
||||
/* ScanSettings.CALLBACK_TYPE_FIRST_MATCH seems to trigger a bug returning an error of
|
||||
SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES (error #5)
|
||||
*/
|
||||
val settings =
|
||||
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).
|
||||
// setMatchMode(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT).
|
||||
// setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH).
|
||||
build()
|
||||
scanner.startScan(listOf(filter), settings, scanCallback)
|
||||
|
||||
onDispose {
|
||||
BTLog.debug("stopping scan")
|
||||
scanner.stopScan(scanCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Text("FIXME")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun btScanScreenPreview() {
|
||||
BTScanScreen()
|
||||
}
|
|
@ -20,9 +20,6 @@ import com.geeksville.mesh.R
|
|||
@Composable
|
||||
fun HomeContent() {
|
||||
Column {
|
||||
Text(text = "Meshtastic")
|
||||
|
||||
|
||||
Row {
|
||||
Container(LayoutSize(40.dp, 40.dp)) {
|
||||
VectorImage(id = if (UIState.isConnected.value) R.drawable.cloud_on else R.drawable.cloud_off)
|
||||
|
@ -57,23 +54,6 @@ fun HomeContent() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(openDrawer: () -> Unit) {
|
||||
Column {
|
||||
TopAppBar(
|
||||
title = { Text(text = "Meshtastic") },
|
||||
navigationIcon = {
|
||||
VectorImageButton(R.drawable.ic_launcher_new_foreground) {
|
||||
openDrawer()
|
||||
}
|
||||
}
|
||||
)
|
||||
VerticalScroller(modifier = LayoutFlexible(1f)) {
|
||||
HomeContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun MeshApp() {
|
||||
|
@ -108,10 +88,26 @@ fun previewView() {
|
|||
private fun AppContent(openDrawer: () -> Unit) {
|
||||
Crossfade(AppStatus.currentScreen) { screen ->
|
||||
Surface(color = (MaterialTheme.colors()).background) {
|
||||
when (screen) {
|
||||
is Screen.Home -> HomeScreen { openDrawer() }
|
||||
/* is Screen.Interests -> InterestsScreen { openDrawer() }
|
||||
is Screen.Article -> ArticleScreen(postId = screen.postId) */
|
||||
|
||||
Column {
|
||||
TopAppBar(
|
||||
title = { Text(text = "Meshtastic") },
|
||||
navigationIcon = {
|
||||
VectorImageButton(R.drawable.ic_launcher_new_foreground) {
|
||||
openDrawer()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
VerticalScroller(modifier = LayoutFlexible(1f)) {
|
||||
when (screen) {
|
||||
is Screen.Home -> HomeContent()
|
||||
is Screen.SelectRadio -> BTScanScreen()
|
||||
// Question: how to get hooks invoked when this screen gets shown/removed?
|
||||
// i.e. I need to start/stop a bluetooth scan operation. depending on the
|
||||
// appearance/disappearance of this screen.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,25 +129,21 @@ private fun AppDrawer(
|
|||
VectorImage(id = R.drawable.ic_launcher_new_foreground)
|
||||
}
|
||||
Divider(color = Color(0x14333333))
|
||||
DrawerButton(
|
||||
icon = R.drawable.ic_launcher_new_foreground,
|
||||
label = "Home",
|
||||
isSelected = currentScreen == Screen.Home
|
||||
) {
|
||||
navigateTo(Screen.Home)
|
||||
closeDrawer()
|
||||
|
||||
@Composable
|
||||
fun ScreenButton(icon: Int, label: String, screen: Screen) {
|
||||
DrawerButton(
|
||||
icon = icon,
|
||||
label = label,
|
||||
isSelected = currentScreen == screen
|
||||
) {
|
||||
navigateTo(screen)
|
||||
closeDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
DrawerButton(
|
||||
icon = R.drawable.ic_interests,
|
||||
label = "Interests",
|
||||
isSelected = currentScreen == Screen.Interests
|
||||
) {
|
||||
navigateTo(Screen.Interests)
|
||||
closeDrawer()
|
||||
}
|
||||
*/
|
||||
ScreenButton(R.drawable.ic_launcher_new_foreground, "Home", Screen.Home)
|
||||
ScreenButton(R.drawable.ic_launcher_new_foreground, "Setup", Screen.SelectRadio)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import java.util.*
|
|||
// defines the screens we have in the app
|
||||
sealed class Screen {
|
||||
object Home : Screen()
|
||||
// object Settings : Screen()
|
||||
object SelectRadio : Screen()
|
||||
}
|
||||
|
||||
@Model
|
||||
|
@ -22,7 +22,7 @@ data class TextMessage(val date: Date, val from: String, val text: String)
|
|||
|
||||
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
||||
object UIState {
|
||||
|
||||
|
||||
private val testPositions = arrayOf(
|
||||
Position(32.776665, -96.796989, 35), // dallas
|
||||
Position(32.960758, -96.733521, 35), // richardson
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
*DeviceState.receive_queue max_count:32
|
||||
|
||||
# FIXME, max out based on total SubPacket size And do fragmentation and reassembly (for larger payloads) at the Android layer, not the esp32 layer.
|
||||
*Data.payload max_size:200
|
||||
*Data.payload max_size:251
|
||||
|
||||
# 128 bit psk key (we don't use 256 bit yet because we want to keep our QR code small)
|
||||
*ChannelSettings.psk max_size:16 fixed_length:true
|
||||
|
|
|
@ -301,7 +301,11 @@ message DeviceState {
|
|||
Current = 11;
|
||||
};
|
||||
|
||||
/// A version integer used to invalidate old save files when we make incompatible changes
|
||||
Version version = 6;
|
||||
|
||||
/// We keep the last received text message (only) stored in the device flash, so we can show it on the screen. Might be null
|
||||
MeshPacket rx_text_message = 7;
|
||||
}
|
||||
|
||||
// packets from the radio to the phone will appear on the fromRadio characteristic. It will support
|
||||
|
|
Ładowanie…
Reference in New Issue