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
|
# High priority
|
||||||
MVP features required for first public alpha
|
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
|
* 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
|
* let the user type texts somewhere
|
||||||
* include a background behind our cloud graphics, so redraws work properly
|
* 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
|
* 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
|
* 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
|
* 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
|
* call crashlytics from exceptionReporter!!! currently not logging failures caught there
|
||||||
* test with oldest compatible android in emulator (see below for testing with hardware)
|
* test with oldest compatible android in emulator (see below for testing with hardware)
|
||||||
* make playstore entry, first public alpha
|
* 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
|
* when notified phone should automatically download messages
|
||||||
* use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state
|
* use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state
|
||||||
* all chat in the app defaults to group chat
|
* 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 */
|
/* Do this better FIXME */
|
||||||
val usetbeam = false
|
val usetbeam = false
|
||||||
val address = if (usetbeam) "B4:E6:2D:EA:32:B7" else "24:6F:28:96:C9:2A"
|
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()
|
requestPermission()
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,9 @@ class RadioInterfaceService : Service(), Logging {
|
||||||
*/
|
*/
|
||||||
const val RADIO_CONNECTED_ACTION = "$prefix.CONNECT_CHANGED"
|
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 =
|
private val BTM_FROMRADIO_CHARACTER =
|
||||||
UUID.fromString("8ba2bcc2-ee02-4a55-a531-c525c5e454d5")
|
UUID.fromString("8ba2bcc2-ee02-4a55-a531-c525c5e454d5")
|
||||||
private val BTM_TORADIO_CHARACTER =
|
private val BTM_TORADIO_CHARACTER =
|
||||||
|
@ -131,11 +133,19 @@ class RadioInterfaceService : Service(), Logging {
|
||||||
private fun getPrefs(context: Context) =
|
private fun getPrefs(context: Context) =
|
||||||
context.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
|
context.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
/// Return the device we are configured to use, or null for none
|
private const val DEVADDR_KEY = "devAddr"
|
||||||
fun getBondedDeviceAddress(context: Context) = getPrefs(context).getString("devAddr", null)
|
|
||||||
|
|
||||||
fun setBondedDeviceAddress(context: Context, addr: String) =
|
/// Return the device we are configured to use, or null for none
|
||||||
getPrefs(context).edit(commit = true) { putString("devAddr", addr) }
|
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) {
|
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
|
@Composable
|
||||||
fun HomeContent() {
|
fun HomeContent() {
|
||||||
Column {
|
Column {
|
||||||
Text(text = "Meshtastic")
|
|
||||||
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
Container(LayoutSize(40.dp, 40.dp)) {
|
Container(LayoutSize(40.dp, 40.dp)) {
|
||||||
VectorImage(id = if (UIState.isConnected.value) R.drawable.cloud_on else R.drawable.cloud_off)
|
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
|
@Composable
|
||||||
fun MeshApp() {
|
fun MeshApp() {
|
||||||
|
@ -108,10 +88,26 @@ fun previewView() {
|
||||||
private fun AppContent(openDrawer: () -> Unit) {
|
private fun AppContent(openDrawer: () -> Unit) {
|
||||||
Crossfade(AppStatus.currentScreen) { screen ->
|
Crossfade(AppStatus.currentScreen) { screen ->
|
||||||
Surface(color = (MaterialTheme.colors()).background) {
|
Surface(color = (MaterialTheme.colors()).background) {
|
||||||
when (screen) {
|
|
||||||
is Screen.Home -> HomeScreen { openDrawer() }
|
Column {
|
||||||
/* is Screen.Interests -> InterestsScreen { openDrawer() }
|
TopAppBar(
|
||||||
is Screen.Article -> ArticleScreen(postId = screen.postId) */
|
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)
|
VectorImage(id = R.drawable.ic_launcher_new_foreground)
|
||||||
}
|
}
|
||||||
Divider(color = Color(0x14333333))
|
Divider(color = Color(0x14333333))
|
||||||
DrawerButton(
|
|
||||||
icon = R.drawable.ic_launcher_new_foreground,
|
@Composable
|
||||||
label = "Home",
|
fun ScreenButton(icon: Int, label: String, screen: Screen) {
|
||||||
isSelected = currentScreen == Screen.Home
|
DrawerButton(
|
||||||
) {
|
icon = icon,
|
||||||
navigateTo(Screen.Home)
|
label = label,
|
||||||
closeDrawer()
|
isSelected = currentScreen == screen
|
||||||
|
) {
|
||||||
|
navigateTo(screen)
|
||||||
|
closeDrawer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
ScreenButton(R.drawable.ic_launcher_new_foreground, "Home", Screen.Home)
|
||||||
DrawerButton(
|
ScreenButton(R.drawable.ic_launcher_new_foreground, "Setup", Screen.SelectRadio)
|
||||||
icon = R.drawable.ic_interests,
|
|
||||||
label = "Interests",
|
|
||||||
isSelected = currentScreen == Screen.Interests
|
|
||||||
) {
|
|
||||||
navigateTo(Screen.Interests)
|
|
||||||
closeDrawer()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import java.util.*
|
||||||
// defines the screens we have in the app
|
// defines the screens we have in the app
|
||||||
sealed class Screen {
|
sealed class Screen {
|
||||||
object Home : Screen()
|
object Home : Screen()
|
||||||
// object Settings : Screen()
|
object SelectRadio : Screen()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model
|
@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
|
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
||||||
object UIState {
|
object UIState {
|
||||||
|
|
||||||
private val testPositions = arrayOf(
|
private val testPositions = arrayOf(
|
||||||
Position(32.776665, -96.796989, 35), // dallas
|
Position(32.776665, -96.796989, 35), // dallas
|
||||||
Position(32.960758, -96.733521, 35), // richardson
|
Position(32.960758, -96.733521, 35), // richardson
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
*DeviceState.receive_queue max_count:32
|
*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.
|
# 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)
|
# 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
|
*ChannelSettings.psk max_size:16 fixed_length:true
|
||||||
|
|
|
@ -301,7 +301,11 @@ message DeviceState {
|
||||||
Current = 11;
|
Current = 11;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A version integer used to invalidate old save files when we make incompatible changes
|
||||||
Version version = 6;
|
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
|
// packets from the radio to the phone will appear on the fromRadio characteristic. It will support
|
||||||
|
|
Ładowanie…
Reference in New Issue