kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
WIP - cleaned up BLE vs serial interface approximately works
rodzic
e99e8695fe
commit
30137efc68
|
@ -100,12 +100,12 @@
|
|||
|
||||
<!-- This is a private service which just does direct communication to the radio -->
|
||||
<service
|
||||
android:name="com.geeksville.mesh.service.BluetoothInterfaceService"
|
||||
android:name="com.geeksville.mesh.service.RadioInterfaceService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- This is a private service which just does direct communication to the radio -->
|
||||
<service
|
||||
<!-- <service
|
||||
android:name="com.geeksville.mesh.service.SerialInterfaceService"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
|
@ -115,7 +115,7 @@
|
|||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/device_filter" />
|
||||
</service>
|
||||
</service> -->
|
||||
|
||||
<activity
|
||||
android:name="com.geeksville.mesh.MainActivity"
|
||||
|
|
|
@ -167,7 +167,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
|
||||
|
||||
private val btStateReceiver = BluetoothStateReceiver { enabled ->
|
||||
private val btStateReceiver = BluetoothStateReceiver { _ ->
|
||||
updateBluetoothEnabled()
|
||||
}
|
||||
|
||||
|
@ -638,7 +638,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
MeshService.ACTION_MESSAGE_STATUS -> {
|
||||
debug("received message status from service")
|
||||
val id = intent.getIntExtra(EXTRA_PACKET_ID, 0)
|
||||
val status = intent.getParcelableExtra<MessageStatus>(EXTRA_STATUS)
|
||||
val status = intent.getParcelableExtra<MessageStatus>(EXTRA_STATUS)!!
|
||||
|
||||
model.messagesState.updateStatus(id, status)
|
||||
}
|
||||
|
@ -733,8 +733,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
|
||||
bindMeshService()
|
||||
|
||||
val bonded =
|
||||
BluetoothInterfaceService.getBondedDeviceAddress(this) != null
|
||||
val bonded = RadioInterfaceService.getBondedDeviceAddress(this) != null
|
||||
if (!bonded)
|
||||
pager.currentItem = 5
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
package com.geeksville.mesh.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattService
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.geeksville.analytics.DataPair
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
|
@ -58,18 +55,6 @@ callback was called, but before it arrives at the phone. If the phone writes to
|
|||
When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio.
|
||||
Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted.
|
||||
|
||||
meshMyNodeCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ)
|
||||
mynode - read/write this to access a MyNodeInfo protobuf
|
||||
|
||||
meshNodeInfoCharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ),
|
||||
nodeinfo - read this to get a series of node infos (ending with a null empty record), write to this to restart the read statemachine that returns all the node infos
|
||||
|
||||
meshRadioCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ),
|
||||
radio - read/write this to access a RadioConfig protobuf
|
||||
|
||||
meshOwnerCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
|
||||
owner - read/write this to access a User protobuf
|
||||
|
||||
Re: queue management
|
||||
Not all messages are kept in the fromradio queue (filtered based on SubPacket):
|
||||
* only the most recent Position and User messages for a particular node are kept
|
||||
|
@ -91,7 +76,8 @@ A variable keepAllPackets, if set to true will suppress this behavior and instea
|
|||
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
|
||||
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
class BluetoothInterfaceService : InterfaceService() {
|
||||
class BluetoothInterface(val service: RadioInterfaceService, val address: String) : IRadioInterface,
|
||||
Logging {
|
||||
|
||||
companion object : Logging {
|
||||
|
||||
|
@ -104,9 +90,6 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
UUID.fromString("f75c76d2-129e-4dad-a1dd-7866124401e7")
|
||||
val BTM_FROMNUM_CHARACTER =
|
||||
UUID.fromString("ed9da18c-a800-4f66-a670-aa7547e34453")
|
||||
|
||||
/// If our service is currently running, this pointer can be used to reach it (in case setBondedDeviceAddress is called)
|
||||
private var runningService: BluetoothInterfaceService? = null
|
||||
|
||||
/// Get our bluetooth adapter (should always succeed except on emulator
|
||||
private fun getBluetoothAdapter(context: Context): BluetoothAdapter? {
|
||||
|
@ -115,7 +98,21 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
return bluetoothManager.adapter
|
||||
}
|
||||
|
||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||
fun addressValid(context: Context, address: String): Boolean {
|
||||
val allPaired =
|
||||
getBluetoothAdapter(context)?.bondedDevices.orEmpty().map { it.address }.toSet()
|
||||
|
||||
return if (!allPaired.contains(address)) {
|
||||
warn("Ignoring stale bond to ${address.anonymize}")
|
||||
false
|
||||
} else
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
/// Return the device we are configured to use, or null for none
|
||||
/*
|
||||
@SuppressLint("NewApi")
|
||||
fun getBondedDeviceAddress(context: Context): String? =
|
||||
if (hasCompanionDeviceApi(context)) {
|
||||
|
@ -133,7 +130,7 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
getBluetoothAdapter(context)?.bondedDevices.orEmpty().map { it.address }.toSet()
|
||||
|
||||
// If the user has unpaired our device, treat things as if we don't have one
|
||||
val address = getPrefs(context).getString(DEVADDR_KEY, null)
|
||||
val address = InterfaceService.getPrefs(context).getString(DEVADDR_KEY, null)
|
||||
|
||||
if (address != null && !allPaired.contains(address)) {
|
||||
warn("Ignoring stale bond to ${address.anonymize}")
|
||||
|
@ -141,7 +138,7 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
} else
|
||||
address
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
/// Can we use the modern BLE scan API?
|
||||
fun hasCompanionDeviceApi(context: Context): Boolean = false /* ALAS - not ready for production yet
|
||||
|
@ -155,6 +152,21 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
false
|
||||
} */
|
||||
|
||||
/** FIXME - when adding companion device support back in, use this code to set companion device from setBondedDevice
|
||||
* if (BluetoothInterface.hasCompanionDeviceApi(this)) {
|
||||
// We only keep an association to one device at a time...
|
||||
if (addr != null) {
|
||||
val deviceManager = getSystemService(CompanionDeviceManager::class.java)
|
||||
|
||||
deviceManager.associations.forEach { old ->
|
||||
if (addr != old) {
|
||||
BluetoothInterface.debug("Forgetting old BLE association $old")
|
||||
deviceManager.disassociate(old)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* this is created in onCreate()
|
||||
* We do an ugly hack of keeping it in the singleton so we can share it for the rare software update case
|
||||
|
@ -167,7 +179,7 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
val device get() = safe?.gatt ?: throw RadioNotConnectedException("No GATT")
|
||||
|
||||
/// Our service - note - it is possible to get back a null response for getService if the device services haven't yet been found
|
||||
val service
|
||||
val bservice
|
||||
get(): BluetoothGattService = device.getService(BTM_SERVICE_UUID)
|
||||
?: throw RadioNotConnectedException("BLE service not found")
|
||||
|
||||
|
@ -179,6 +191,23 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
*/
|
||||
private var isFirstSend = true
|
||||
|
||||
init {
|
||||
// Note: this call does no comms, it just creates the device object (even if the
|
||||
// device is off/not connected)
|
||||
val device = getBluetoothAdapter(service)?.getRemoteDevice(address)
|
||||
if (device != null) {
|
||||
info("Creating radio interface service. device=${address.anonymize}")
|
||||
|
||||
// Note this constructor also does no comm
|
||||
val s = SafeBluetooth(service, device)
|
||||
safe = s
|
||||
|
||||
startConnect()
|
||||
} else {
|
||||
errormsg("Bluetooth adapter not found, assuming running on the emulator!")
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a packet/command out the radio link
|
||||
override fun handleSendToRadio(p: ByteArray) {
|
||||
try {
|
||||
|
@ -187,10 +216,6 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
BTM_TORADIO_CHARACTER,
|
||||
p
|
||||
) // Do a synchronous write, so that we can then do our reads if needed
|
||||
if (logSends) {
|
||||
sentPacketsLog.write(p)
|
||||
sentPacketsLog.flush()
|
||||
}
|
||||
|
||||
if (isFirstSend) {
|
||||
isFirstSend = false
|
||||
|
@ -204,18 +229,16 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
|
||||
/// Attempt to read from the fromRadio mailbox, if data is found broadcast it to android apps
|
||||
private fun doReadFromRadio(firstRead: Boolean) {
|
||||
if (!isConnected)
|
||||
warn("Abandoning fromradio read because we are not connected")
|
||||
else {
|
||||
safe?.let { s ->
|
||||
val fromRadio = getCharacteristic(BTM_FROMRADIO_CHARACTER)
|
||||
safe!!.asyncReadCharacteristic(fromRadio) {
|
||||
s.asyncReadCharacteristic(fromRadio) {
|
||||
try {
|
||||
val b = it.getOrThrow()
|
||||
.value.clone() // We clone the array just in case, I'm not sure if they keep reusing the array
|
||||
|
||||
if (b.isNotEmpty()) {
|
||||
debug("Received ${b.size} bytes from radio")
|
||||
handleFromRadio(b)
|
||||
service.handleFromRadio(b)
|
||||
|
||||
// Queue up another read, until we run out of packets
|
||||
doReadFromRadio(firstRead)
|
||||
|
@ -229,56 +252,12 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
"error during doReadFromRadio",
|
||||
ex
|
||||
)
|
||||
serviceScope.handledLaunch { retryDueToException() }
|
||||
service.serviceScope.handledLaunch { retryDueToException() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun setBondedDeviceAddress(addr: String?) {
|
||||
// Record that this use has configured a radio
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"mesh_bond"
|
||||
)
|
||||
|
||||
// Ignore any errors that happen while closing old device
|
||||
ignoreException {
|
||||
Companion.info("shutting down old service")
|
||||
setEnabled(false) // nasty, needed to force the next setEnabled call to reconnect
|
||||
}
|
||||
|
||||
debug("Setting bonded device to $addr")
|
||||
if (hasCompanionDeviceApi(this)) {
|
||||
// We only keep an association to one device at a time...
|
||||
if (addr != null) {
|
||||
val deviceManager = getSystemService(CompanionDeviceManager::class.java)
|
||||
|
||||
deviceManager.associations.forEach { old ->
|
||||
if (addr != old) {
|
||||
Companion.debug("Forgetting old BLE association $old")
|
||||
deviceManager.disassociate(old)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getPrefs(this).edit(commit = true) {
|
||||
if (addr == null)
|
||||
this.remove(DEVADDR_KEY)
|
||||
else
|
||||
putString(DEVADDR_KEY, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Force the service to reconnect
|
||||
if (addr != null) {
|
||||
info("Setting enable on the running radio service")
|
||||
setEnabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Android caches old services. But our service is still changing often, so force it to reread the service definitions every
|
||||
* time
|
||||
|
@ -302,7 +281,6 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Some buggy BLE stacks can fail on initial connect, with either missing services or missing characteristics. If that happens we
|
||||
* disconnect and try again when the device reenumerates.
|
||||
|
@ -341,7 +319,7 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
safe!!.asyncDiscoverServices { discRes ->
|
||||
discRes.getOrThrow() // FIXME, instead just try to reconnect?
|
||||
|
||||
serviceScope.handledLaunch {
|
||||
service.serviceScope.handledLaunch {
|
||||
try {
|
||||
debug("Discovered services!")
|
||||
delay(1000) // android BLE is buggy and needs a 500ms sleep before calling getChracteristic, or you might get back null
|
||||
|
@ -353,14 +331,11 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
|
||||
fromNum = getCharacteristic(BTM_FROMNUM_CHARACTER)
|
||||
|
||||
// We must set this to true before broadcasting connectionChanged
|
||||
isConnected = true
|
||||
|
||||
// We treat the first send by a client as special
|
||||
isFirstSend = true
|
||||
|
||||
// Now tell clients they can (finally use the api)
|
||||
broadcastConnectionChanged(true, isPermanent = false)
|
||||
service.broadcastConnectionChanged(true, isPermanent = false)
|
||||
|
||||
// Immediately broadcast any queued packets sitting on the device
|
||||
doReadFromRadio(true)
|
||||
|
@ -403,34 +378,27 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
ex
|
||||
)
|
||||
shouldSetMtu = false
|
||||
serviceScope.handledLaunch { retryDueToException() }
|
||||
service.serviceScope.handledLaunch { retryDueToException() }
|
||||
}
|
||||
}
|
||||
else
|
||||
doDiscoverServicesAndInit()
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user turns on bluetooth after we start, make sure to try and reconnected then
|
||||
*/
|
||||
private val bluetoothStateReceiver = BluetoothStateReceiver { enabled ->
|
||||
if (enabled)
|
||||
setEnabled(true)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
runningService = this
|
||||
super.onCreate()
|
||||
registerReceiver(bluetoothStateReceiver, bluetoothStateReceiver.intent)
|
||||
}
|
||||
override fun close() {
|
||||
if (safe != null) {
|
||||
info("Closing radio interface service")
|
||||
val s = safe
|
||||
safe =
|
||||
null // We do this first, because if we throw we still want to mark that we no longer have a valid connection
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterReceiver(bluetoothStateReceiver)
|
||||
runningService = null
|
||||
super.onDestroy()
|
||||
s?.close()
|
||||
} else {
|
||||
debug("Radio was not connected, skipping disable")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Start a connection attempt
|
||||
private fun startConnect() {
|
||||
// we pass in true for autoconnect - so we will autoconnect whenever the radio
|
||||
|
@ -439,57 +407,14 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
// more info
|
||||
safe!!.asyncConnect(true, // FIXME, sometimes this seems to not work or take a very long time!
|
||||
cb = ::onConnect,
|
||||
lostConnectCb = { onDisconnect(isPermanent = false) })
|
||||
}
|
||||
|
||||
/// Open or close a bluetooth connection to our device
|
||||
override fun setEnabled(on: Boolean) {
|
||||
super.setEnabled(on)
|
||||
if (on) {
|
||||
if (safe != null) {
|
||||
info("Skipping radio enable, it is already on")
|
||||
} else {
|
||||
val address = getBondedDeviceAddress(this)
|
||||
if (address == null)
|
||||
errormsg("No bonded mesh radio, can't start service")
|
||||
else {
|
||||
// Note: this call does no comms, it just creates the device object (even if the
|
||||
// device is off/not connected)
|
||||
val device = getBluetoothAdapter(this)?.getRemoteDevice(address)
|
||||
if (device != null) {
|
||||
info("Creating radio interface service. device=${address.anonymize}")
|
||||
|
||||
// Note this constructor also does no comm
|
||||
val s = SafeBluetooth(this, device)
|
||||
safe = s
|
||||
|
||||
startConnect()
|
||||
} else {
|
||||
errormsg("Bluetooth adapter not found, assuming running on the emulator!")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (safe != null) {
|
||||
info("Closing radio interface service")
|
||||
val s = safe
|
||||
safe =
|
||||
null // We do this first, because if we throw we still want to mark that we no longer have a valid connection
|
||||
|
||||
s?.close()
|
||||
} else {
|
||||
debug("Radio was not connected, skipping disable")
|
||||
}
|
||||
}
|
||||
lostConnectCb = { service.onDisconnect(isPermanent = false) })
|
||||
}
|
||||
|
||||
/**
|
||||
* do a synchronous write operation
|
||||
*/
|
||||
private fun doWrite(uuid: UUID, a: ByteArray) = toRemoteExceptions {
|
||||
if (!isConnected)
|
||||
throw RadioNotConnectedException()
|
||||
else {
|
||||
safe?.let { s ->
|
||||
debug("queuing ${a.size} bytes to $uuid")
|
||||
|
||||
// Note: we generate a new characteristic each time, because we are about to
|
||||
|
@ -497,7 +422,7 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
val toRadio = getCharacteristic(uuid)
|
||||
toRadio.value = a
|
||||
|
||||
safe!!.writeCharacteristic(toRadio)
|
||||
s.writeCharacteristic(toRadio)
|
||||
debug("write of ${a.size} bytes completed")
|
||||
}
|
||||
}
|
||||
|
@ -506,6 +431,6 @@ class BluetoothInterfaceService : InterfaceService() {
|
|||
* Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null
|
||||
*/
|
||||
private fun getCharacteristic(uuid: UUID) =
|
||||
service.getCharacteristic(uuid) ?: throw BLEException("Can't get characteristic $uuid")
|
||||
bservice.getCharacteristic(uuid) ?: throw BLEException("Can't get characteristic $uuid")
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.geeksville.mesh.service
|
||||
|
||||
import java.io.Closeable
|
||||
|
||||
interface IRadioInterface : Closeable {
|
||||
fun handleSendToRadio(p: ByteArray)
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
package com.geeksville.mesh.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.geeksville.android.BinaryLogFile
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.IRadioInterfaceService
|
||||
import com.geeksville.util.toRemoteExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
|
||||
class RadioNotConnectedException(message: String = "Not connected to radio") :
|
||||
BLEException(message)
|
||||
|
||||
|
||||
/**
|
||||
* Handles the bluetooth link with a mesh radio device. Does not cache any device state,
|
||||
* just does bluetooth comms etc...
|
||||
*
|
||||
* This service is not exposed outside of this process.
|
||||
*
|
||||
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
|
||||
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
abstract class InterfaceService : Service(), Logging {
|
||||
|
||||
companion object : Logging {
|
||||
/**
|
||||
* The RECEIVED_FROMRADIO
|
||||
* Payload will be the raw bytes which were contained within a MeshProtos.FromRadio protobuf
|
||||
*/
|
||||
const val RECEIVE_FROMRADIO_ACTION = "$prefix.RECEIVE_FROMRADIO"
|
||||
|
||||
/**
|
||||
* This is broadcast when connection state changed
|
||||
*/
|
||||
const val RADIO_CONNECTED_ACTION = "$prefix.CONNECT_CHANGED"
|
||||
|
||||
const val DEVADDR_KEY = "devAddr"
|
||||
|
||||
/// This is public only so that SimRadio can bootstrap our message flow
|
||||
fun broadcastReceivedFromRadio(context: Context, payload: ByteArray) {
|
||||
val intent = Intent(RECEIVE_FROMRADIO_ACTION)
|
||||
intent.putExtra(EXTRA_PAYLOAD, payload)
|
||||
context.sendBroadcast(intent)
|
||||
}
|
||||
|
||||
fun getPrefs(context: Context) =
|
||||
context.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
protected val logSends = false
|
||||
protected val logReceives = false
|
||||
protected lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate
|
||||
protected lateinit var receivedPacketsLog: BinaryLogFile
|
||||
|
||||
protected var isConnected = false
|
||||
|
||||
private val serviceJob = Job()
|
||||
protected val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||
|
||||
protected fun broadcastConnectionChanged(isConnected: Boolean, isPermanent: Boolean) {
|
||||
debug("Broadcasting connection=$isConnected")
|
||||
val intent = Intent(RADIO_CONNECTED_ACTION)
|
||||
intent.putExtra(EXTRA_CONNECTED, isConnected)
|
||||
intent.putExtra(EXTRA_PERMANENT, isPermanent)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
/// Send a packet/command out the radio link, this routine can block if it needs to
|
||||
protected abstract fun handleSendToRadio(p: ByteArray)
|
||||
|
||||
// Handle an incoming packet from the radio, broadcasts it as an android intent
|
||||
protected fun handleFromRadio(p: ByteArray) {
|
||||
if (logReceives) {
|
||||
receivedPacketsLog.write(p)
|
||||
receivedPacketsLog.flush()
|
||||
}
|
||||
|
||||
broadcastReceivedFromRadio(
|
||||
this,
|
||||
p
|
||||
)
|
||||
}
|
||||
|
||||
protected fun onDisconnect(isPermanent: Boolean) {
|
||||
broadcastConnectionChanged(false, isPermanent)
|
||||
isConnected = false
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
setEnabled(true)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
setEnabled(false)
|
||||
serviceJob.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return binder;
|
||||
}
|
||||
|
||||
|
||||
/// Open or close a bluetooth connection to our device
|
||||
protected open fun setEnabled(on: Boolean) {
|
||||
if (on) {
|
||||
if (logSends)
|
||||
sentPacketsLog = BinaryLogFile(this, "sent_log.pb")
|
||||
if (logReceives)
|
||||
receivedPacketsLog = BinaryLogFile(this, "receive_log.pb")
|
||||
} else {
|
||||
if (logSends)
|
||||
sentPacketsLog.close()
|
||||
if (logReceives)
|
||||
receivedPacketsLog.close()
|
||||
|
||||
onDisconnect(isPermanent = true) // Tell any clients we are now offline
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun setBondedDeviceAddress(addr: String?) {
|
||||
TODO()
|
||||
}
|
||||
|
||||
private val binder = object : IRadioInterfaceService.Stub() {
|
||||
|
||||
override fun setDeviceAddress(deviceAddr: String?) = toRemoteExceptions {
|
||||
setBondedDeviceAddress(deviceAddr)
|
||||
}
|
||||
|
||||
override fun sendToRadio(a: ByteArray) {
|
||||
// Do this in the IO thread because it might take a while (and we don't care about the result code)
|
||||
serviceScope.handledLaunch { handleSendToRadio(a) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,6 +51,9 @@ class MeshService : Service(), Logging {
|
|||
|
||||
companion object : Logging {
|
||||
|
||||
/// special broadcast address
|
||||
const val NODENUM_BROADCAST = (0xffffffff).toInt()
|
||||
|
||||
/// Intents broadcast by MeshService
|
||||
const val ACTION_RECEIVED_DATA = "$prefix.RECEIVED_DATA"
|
||||
const val ACTION_NODE_CHANGE = "$prefix.NODE_CHANGE"
|
||||
|
@ -445,12 +448,12 @@ class MeshService : Service(), Logging {
|
|||
|
||||
// we listen for messages from the radio receiver _before_ trying to create the service
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(InterfaceService.RECEIVE_FROMRADIO_ACTION)
|
||||
filter.addAction(InterfaceService.RADIO_CONNECTED_ACTION)
|
||||
filter.addAction(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION)
|
||||
filter.addAction(RadioInterfaceService.RADIO_CONNECTED_ACTION)
|
||||
registerReceiver(radioInterfaceReceiver, filter)
|
||||
|
||||
// We in turn need to use the radiointerface service
|
||||
val intent = Intent(this@MeshService, BluetoothInterfaceService::class.java)
|
||||
val intent = Intent(this@MeshService, RadioInterfaceService::class.java)
|
||||
// intent.action = IMeshService::class.java.name
|
||||
radio.connect(this@MeshService, intent, Context.BIND_AUTO_CREATE)
|
||||
|
||||
|
@ -479,9 +482,6 @@ class MeshService : Service(), Logging {
|
|||
/// BEGINNING OF MODEL - FIXME, move elsewhere
|
||||
///
|
||||
|
||||
/// special broadcast address
|
||||
val NODENUM_BROADCAST = 255
|
||||
|
||||
/// Our saved preferences as stored on disk
|
||||
@Serializable
|
||||
private data class SavedSettings(
|
||||
|
@ -1086,7 +1086,7 @@ class MeshService : Service(), Logging {
|
|||
serviceScope.handledLaunch {
|
||||
debug("Received broadcast ${intent.action}")
|
||||
when (intent.action) {
|
||||
InterfaceService.RADIO_CONNECTED_ACTION -> {
|
||||
RadioInterfaceService.RADIO_CONNECTED_ACTION -> {
|
||||
try {
|
||||
val connected = intent.getBooleanExtra(EXTRA_CONNECTED, false)
|
||||
val permanent = intent.getBooleanExtra(EXTRA_PERMANENT, false)
|
||||
|
@ -1103,7 +1103,7 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
InterfaceService.RECEIVE_FROMRADIO_ACTION -> {
|
||||
RadioInterfaceService.RECEIVE_FROMRADIO_ACTION -> {
|
||||
val proto =
|
||||
MeshProtos.FromRadio.parseFrom(
|
||||
intent.getByteArrayExtra(
|
||||
|
@ -1403,7 +1403,7 @@ class MeshService : Service(), Logging {
|
|||
// Run in the IO thread
|
||||
val filename = firmwareUpdateFilename ?: throw Exception("No update filename")
|
||||
val safe =
|
||||
BluetoothInterfaceService.safe
|
||||
BluetoothInterface.safe
|
||||
?: throw Exception("Can't update - no bluetooth connected")
|
||||
|
||||
serviceScope.handledLaunch {
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
package com.geeksville.mesh.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.IBinder
|
||||
import androidx.core.content.edit
|
||||
import com.geeksville.android.BinaryLogFile
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.IRadioInterfaceService
|
||||
import com.geeksville.util.ignoreException
|
||||
import com.geeksville.util.toRemoteExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
|
||||
class RadioNotConnectedException(message: String = "Not connected to radio") :
|
||||
BLEException(message)
|
||||
|
||||
|
||||
/**
|
||||
* Handles the bluetooth link with a mesh radio device. Does not cache any device state,
|
||||
* just does bluetooth comms etc...
|
||||
*
|
||||
* This service is not exposed outside of this process.
|
||||
*
|
||||
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
|
||||
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
class RadioInterfaceService : Service(), Logging {
|
||||
|
||||
companion object : Logging {
|
||||
/**
|
||||
* The RECEIVED_FROMRADIO
|
||||
* Payload will be the raw bytes which were contained within a MeshProtos.FromRadio protobuf
|
||||
*/
|
||||
const val RECEIVE_FROMRADIO_ACTION = "$prefix.RECEIVE_FROMRADIO"
|
||||
|
||||
/**
|
||||
* This is broadcast when connection state changed
|
||||
*/
|
||||
const val RADIO_CONNECTED_ACTION = "$prefix.CONNECT_CHANGED"
|
||||
|
||||
const val DEVADDR_KEY_OLD = "devAddr"
|
||||
const val DEVADDR_KEY = "devAddr2" // the new name for devaddr
|
||||
|
||||
/// This is public only so that SimRadio can bootstrap our message flow
|
||||
fun broadcastReceivedFromRadio(context: Context, payload: ByteArray) {
|
||||
val intent = Intent(RECEIVE_FROMRADIO_ACTION)
|
||||
intent.putExtra(EXTRA_PAYLOAD, payload)
|
||||
context.sendBroadcast(intent)
|
||||
}
|
||||
|
||||
fun getPrefs(context: Context): SharedPreferences =
|
||||
context.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
|
||||
|
||||
/** Return the device we are configured to use, or null for none
|
||||
* device address strings are of the form:
|
||||
*
|
||||
* at
|
||||
*
|
||||
* where a is either x for bluetooth or s for serial
|
||||
* and t is an interface specific address (macaddr or a device path)
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun getBondedDeviceAddress(context: Context): String? {
|
||||
// If the user has unpaired our device, treat things as if we don't have one
|
||||
val prefs = getPrefs(context)
|
||||
var address = prefs.getString(DEVADDR_KEY, null)
|
||||
|
||||
if (address == null) { /// Check for the old preferences name we used to use
|
||||
val rest = prefs.getString(DEVADDR_KEY_OLD, null)
|
||||
if (rest != null)
|
||||
address = "x$rest" // Add the bluetooth prefix
|
||||
}
|
||||
|
||||
/// Interfaces can filter addresses to indicate that address is no longer acceptable
|
||||
if (address != null) {
|
||||
val c = address[0]
|
||||
val rest = address.substring(1)
|
||||
val isValid = when (c) {
|
||||
'x' -> BluetoothInterface.addressValid(context, rest)
|
||||
else -> true
|
||||
}
|
||||
if (!isValid)
|
||||
return null
|
||||
}
|
||||
return address
|
||||
}
|
||||
|
||||
/// If our service is currently running, this pointer can be used to reach it (in case setBondedDeviceAddress is called)
|
||||
private var runningService: RadioInterfaceService? = null
|
||||
}
|
||||
|
||||
private val logSends = false
|
||||
private val logReceives = false
|
||||
private lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate
|
||||
private lateinit var receivedPacketsLog: BinaryLogFile
|
||||
|
||||
private val serviceJob = Job()
|
||||
val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||
|
||||
/**
|
||||
* If the user turns on bluetooth after we start, make sure to try and reconnected then
|
||||
*/
|
||||
private val bluetoothStateReceiver = BluetoothStateReceiver { enabled ->
|
||||
if (enabled)
|
||||
startInterface() // If bluetooth just got turned on, try to restart our ble link
|
||||
}
|
||||
|
||||
fun broadcastConnectionChanged(isConnected: Boolean, isPermanent: Boolean) {
|
||||
debug("Broadcasting connection=$isConnected")
|
||||
val intent = Intent(RADIO_CONNECTED_ACTION)
|
||||
intent.putExtra(EXTRA_CONNECTED, isConnected)
|
||||
intent.putExtra(EXTRA_PERMANENT, isPermanent)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
/// Send a packet/command out the radio link, this routine can block if it needs to
|
||||
private fun handleSendToRadio(p: ByteArray) {
|
||||
radioIf?.let { r ->
|
||||
r.handleSendToRadio(p)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle an incoming packet from the radio, broadcasts it as an android intent
|
||||
fun handleFromRadio(p: ByteArray) {
|
||||
if (logReceives) {
|
||||
receivedPacketsLog.write(p)
|
||||
receivedPacketsLog.flush()
|
||||
}
|
||||
|
||||
broadcastReceivedFromRadio(
|
||||
this,
|
||||
p
|
||||
)
|
||||
}
|
||||
|
||||
fun onDisconnect(isPermanent: Boolean) {
|
||||
broadcastConnectionChanged(false, isPermanent)
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
runningService = this
|
||||
super.onCreate()
|
||||
registerReceiver(bluetoothStateReceiver, bluetoothStateReceiver.intent)
|
||||
startInterface()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterReceiver(bluetoothStateReceiver)
|
||||
stopInterface()
|
||||
serviceJob.cancel()
|
||||
runningService = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return binder;
|
||||
}
|
||||
|
||||
|
||||
private var radioIf: IRadioInterface? = null
|
||||
|
||||
/** Start our configured interface (if it isn't already running) */
|
||||
private fun startInterface() {
|
||||
if (radioIf != null)
|
||||
warn("Can't start interface - $radioIf is already running")
|
||||
else {
|
||||
val address = getBondedDeviceAddress(this)
|
||||
if (address == null)
|
||||
warn("No bonded mesh radio, can't start interface")
|
||||
else {
|
||||
info("Starting radio $address")
|
||||
|
||||
if (logSends)
|
||||
sentPacketsLog = BinaryLogFile(this, "sent_log.pb")
|
||||
if (logReceives)
|
||||
receivedPacketsLog = BinaryLogFile(this, "receive_log.pb")
|
||||
|
||||
val c = address[0]
|
||||
val rest = address.substring(1)
|
||||
radioIf = when (c) {
|
||||
'x' -> BluetoothInterface(this, rest)
|
||||
's' -> SerialInterface(this, rest)
|
||||
else -> TODO("Unexpected radio interface type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun stopInterface() {
|
||||
info("stopping interface $radioIf")
|
||||
radioIf?.let { r ->
|
||||
radioIf = null
|
||||
r.close()
|
||||
|
||||
if (logSends)
|
||||
sentPacketsLog.close()
|
||||
if (logReceives)
|
||||
receivedPacketsLog.close()
|
||||
|
||||
onDisconnect(isPermanent = true) // Tell any clients we are now offline
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
fun setBondedDeviceAddress(addr: String?) {
|
||||
// Record that this use has configured a radio
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"mesh_bond"
|
||||
)
|
||||
|
||||
// Ignore any errors that happen while closing old device
|
||||
ignoreException {
|
||||
stopInterface()
|
||||
}
|
||||
|
||||
debug("Setting bonded device to $addr")
|
||||
|
||||
getPrefs(this).edit(commit = true) {
|
||||
this.remove(DEVADDR_KEY_OLD) // remove any old version of the key
|
||||
|
||||
if (addr == null)
|
||||
this.remove(DEVADDR_KEY)
|
||||
else
|
||||
putString(DEVADDR_KEY, addr)
|
||||
}
|
||||
|
||||
// Force the service to reconnect
|
||||
startInterface()
|
||||
}
|
||||
|
||||
private val binder = object : IRadioInterfaceService.Stub() {
|
||||
|
||||
override fun setDeviceAddress(deviceAddr: String?) = toRemoteExceptions {
|
||||
setBondedDeviceAddress(deviceAddr)
|
||||
}
|
||||
|
||||
override fun sendToRadio(a: ByteArray) {
|
||||
// Do this in the IO thread because it might take a while (and we don't care about the result code)
|
||||
serviceScope.handledLaunch { handleSendToRadio(a) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,25 +2,46 @@ package com.geeksville.mesh.service
|
|||
|
||||
import android.content.Context
|
||||
import android.hardware.usb.UsbManager
|
||||
import com.geeksville.android.Logging
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import com.hoho.android.usbserial.driver.UsbSerialPort
|
||||
import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||
|
||||
|
||||
class SerialInterfaceService : InterfaceService() {
|
||||
class SerialInterface(private val service: RadioInterfaceService, val address: String) : Logging,
|
||||
IRadioInterface {
|
||||
companion object {
|
||||
private val START1 = 0x94.toByte()
|
||||
private val START2 = 0xc3.toByte()
|
||||
private val MAX_TO_FROM_RADIO_SIZE = 512
|
||||
private const val START1 = 0x94.toByte()
|
||||
private const val START2 = 0xc3.toByte()
|
||||
private const val MAX_TO_FROM_RADIO_SIZE = 512
|
||||
}
|
||||
|
||||
private var uart: UsbSerialPort? = null
|
||||
|
||||
|
||||
private val manager: UsbManager by lazy {
|
||||
getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
service.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
}
|
||||
|
||||
init {
|
||||
val drivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
|
||||
|
||||
// Open a connection to the first available driver.
|
||||
// Open a connection to the first available driver.
|
||||
val driver: UsbSerialDriver = drivers[0]
|
||||
val connection = manager.openDevice(driver.device)
|
||||
if (connection == null) {
|
||||
// FIXME add UsbManager.requestPermission(driver.getDevice(), ..) handling to activity
|
||||
TODO()
|
||||
} else {
|
||||
val port = driver.ports[0] // Most devices have just one port (port 0)
|
||||
|
||||
port.open(connection)
|
||||
port.setParameters(921600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
|
||||
uart = port
|
||||
|
||||
// FIXME, start reading thread
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleSendToRadio(p: ByteArray) {
|
||||
uart?.apply {
|
||||
|
@ -78,7 +99,7 @@ class SerialInterfaceService : InterfaceService() {
|
|||
if (packetLen <= MAX_TO_FROM_RADIO_SIZE) {
|
||||
val buf = ByteArray(packetLen)
|
||||
read(buf, 0)
|
||||
handleFromRadio(buf)
|
||||
service.handleFromRadio(buf)
|
||||
}
|
||||
nextPtr = 0 // Start parsing the next packet
|
||||
}
|
||||
|
@ -91,33 +112,8 @@ class SerialInterfaceService : InterfaceService() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
val drivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
|
||||
|
||||
// Open a connection to the first available driver.
|
||||
// Open a connection to the first available driver.
|
||||
val driver: UsbSerialDriver = drivers[0]
|
||||
val connection = manager.openDevice(driver.device)
|
||||
if (connection == null) {
|
||||
// FIXME add UsbManager.requestPermission(driver.getDevice(), ..) handling to activity
|
||||
TODO()
|
||||
} else {
|
||||
val port = driver.ports[0] // Most devices have just one port (port 0)
|
||||
|
||||
port.open(connection)
|
||||
port.setParameters(921600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
|
||||
uart = port
|
||||
|
||||
// FIXME, start reading thread
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
override fun close() {
|
||||
uart?.close()
|
||||
uart = null
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.mesh.service.InterfaceService
|
||||
import com.geeksville.mesh.service.RadioInterfaceService
|
||||
|
||||
class SimRadio(private val context: Context) {
|
||||
|
||||
|
@ -45,7 +45,7 @@ class SimRadio(private val context: Context) {
|
|||
}.build()
|
||||
}.build()
|
||||
|
||||
InterfaceService.broadcastReceivedFromRadio(context, fromRadio.toByteArray())
|
||||
RadioInterfaceService.broadcastReceivedFromRadio(context, fromRadio.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,8 +29,9 @@ import com.geeksville.concurrent.handledLaunch
|
|||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.BluetoothInterfaceService
|
||||
import com.geeksville.mesh.service.BluetoothInterface
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.RadioInterfaceService
|
||||
import com.geeksville.util.anonymize
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -98,11 +99,11 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
debug("BTScanModel created")
|
||||
}
|
||||
|
||||
data class BTScanEntry(val name: String, val macAddress: String, val bonded: Boolean) {
|
||||
data class BTScanEntry(val name: String, val address: String, val bonded: Boolean) {
|
||||
// val isSelected get() = macAddress == selectedMacAddr
|
||||
|
||||
override fun toString(): String {
|
||||
return "BTScanEntry(name=${name.anonymize}, addr=${macAddress.anonymize})"
|
||||
return "BTScanEntry(name=${name.anonymize}, addr=${address.anonymize})"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,12 +116,22 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
val bluetoothAdapter =
|
||||
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
|
||||
|
||||
var selectedMacAddr: String? = null
|
||||
var selectedAddress: String? = null
|
||||
val errorText = object : MutableLiveData<String?>(null) {}
|
||||
|
||||
|
||||
private var scanner: BluetoothLeScanner? = null
|
||||
|
||||
/// If this address is for a bluetooth device, return the macaddr portion, else null
|
||||
val selectedBluetooth: String?
|
||||
get() = selectedAddress?.let { a ->
|
||||
if (a[0] == 'x')
|
||||
a.substring(1)
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
private val scanCallback = object : ScanCallback() {
|
||||
override fun onScanFailed(errorCode: Int) {
|
||||
val msg = "Unexpected bluetooth scan failure: $errorCode"
|
||||
|
@ -142,13 +153,13 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
val entry = BTScanEntry(
|
||||
result.device.name
|
||||
?: "unnamed-$addr", // autobug: some devices might not have a name, if someone is running really old device code?
|
||||
addr,
|
||||
"x$addr",
|
||||
isBonded
|
||||
)
|
||||
debug("onScanResult ${entry}")
|
||||
|
||||
// If nothing was selected, by default select the first thing we see
|
||||
if (selectedMacAddr == null && entry.bonded)
|
||||
if (selectedAddress == null && entry.bonded)
|
||||
changeScanSelection(
|
||||
GeeksvilleApplication.currentActivity as MainActivity,
|
||||
addr
|
||||
|
@ -176,23 +187,23 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
*/
|
||||
fun startScan(): Boolean {
|
||||
debug("BTScan component active")
|
||||
selectedMacAddr = BluetoothInterfaceService.getBondedDeviceAddress(context)
|
||||
selectedAddress = RadioInterfaceService.getBondedDeviceAddress(context)
|
||||
|
||||
return if (bluetoothAdapter == null) {
|
||||
warn("No bluetooth adapter. Running under emulation?")
|
||||
|
||||
val testnodes = listOf(
|
||||
BTScanEntry("Meshtastic_ab12", "xx", false),
|
||||
BTScanEntry("Meshtastic_32ac", "xb", true)
|
||||
BTScanEntry("Meshtastic_ab12", "xaa", false),
|
||||
BTScanEntry("Meshtastic_32ac", "xbb", true)
|
||||
)
|
||||
|
||||
devices.value = (testnodes.map { it.macAddress to it }).toMap().toMutableMap()
|
||||
devices.value = (testnodes.map { it.address to it }).toMap().toMutableMap()
|
||||
|
||||
// If nothing was selected, by default select the first thing we see
|
||||
if (selectedMacAddr == null)
|
||||
if (selectedAddress == null)
|
||||
changeScanSelection(
|
||||
GeeksvilleApplication.currentActivity as MainActivity,
|
||||
testnodes.first().macAddress
|
||||
testnodes.first().address
|
||||
)
|
||||
|
||||
true
|
||||
|
@ -212,7 +223,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
// filter and only accept devices that have a sw update service
|
||||
val filter =
|
||||
ScanFilter.Builder()
|
||||
.setServiceUuid(ParcelUuid(BluetoothInterfaceService.BTM_SERVICE_UUID))
|
||||
.setServiceUuid(ParcelUuid(BluetoothInterface.BTM_SERVICE_UUID))
|
||||
.build()
|
||||
|
||||
val settings =
|
||||
|
@ -254,12 +265,12 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
fun onSelected(activity: MainActivity, it: BTScanEntry): Boolean {
|
||||
// If the device is paired, let user select it, otherwise start the pairing flow
|
||||
if (it.bonded) {
|
||||
changeScanSelection(activity, it.macAddress)
|
||||
changeScanSelection(activity, it.address)
|
||||
return true
|
||||
} else {
|
||||
// We ignore missing BT adapters, because it lets us run on the emulator
|
||||
bluetoothAdapter
|
||||
?.getRemoteDevice(it.macAddress)?.let { device ->
|
||||
?.getRemoteDevice(it.address)?.let { device ->
|
||||
requestBonding(activity, device) { state ->
|
||||
if (state == BOND_BONDED) {
|
||||
errorText.value = activity.getString(R.string.pairing_completed)
|
||||
|
@ -283,7 +294,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
/// Change to a new macaddr selection, updating GUI and radio
|
||||
fun changeScanSelection(context: MainActivity, newAddr: String) {
|
||||
info("Changing BT device to ${newAddr.anonymize}")
|
||||
selectedMacAddr = newAddr
|
||||
selectedAddress = newAddr
|
||||
changeDeviceSelection(context, newAddr)
|
||||
}
|
||||
}
|
||||
|
@ -301,7 +312,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
|
||||
|
||||
private val hasCompanionDeviceApi: Boolean by lazy {
|
||||
BluetoothInterfaceService.hasCompanionDeviceApi(requireContext())
|
||||
BluetoothInterface.hasCompanionDeviceApi(requireContext())
|
||||
}
|
||||
|
||||
private val deviceManager: CompanionDeviceManager by lazy {
|
||||
|
@ -431,7 +442,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
b.text = device.name
|
||||
b.id = View.generateViewId()
|
||||
b.isEnabled = enabled
|
||||
b.isChecked = device.macAddress == scanModel.selectedMacAddr
|
||||
b.isChecked = device.address == scanModel.selectedAddress
|
||||
deviceRadioGroup.addView(b)
|
||||
|
||||
// Once we have at least one device, don't show the "looking for" animation - it makes uers think
|
||||
|
@ -486,13 +497,13 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
var hasShownOurDevice = false
|
||||
devices.values.forEach { device ->
|
||||
hasShownOurDevice =
|
||||
hasShownOurDevice || device.macAddress == scanModel.selectedMacAddr
|
||||
hasShownOurDevice || device.address == scanModel.selectedAddress
|
||||
addDeviceButton(device, true)
|
||||
}
|
||||
|
||||
// The device the user is already paired with is offline currently, still show it
|
||||
// it in the list, but greyed out
|
||||
val selectedAddr = scanModel.selectedMacAddr
|
||||
val selectedAddr = scanModel.selectedBluetooth
|
||||
if (!hasShownOurDevice && selectedAddr != null) {
|
||||
val bDevice = scanModel.bluetoothAdapter!!.getRemoteDevice(selectedAddr)
|
||||
if (bDevice.name != null) { // ignore nodes that node have a name, that means we've lost them since they appeared
|
||||
|
@ -507,7 +518,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
}
|
||||
|
||||
val hasBonded =
|
||||
BluetoothInterfaceService.getBondedDeviceAddress(requireContext()) != null
|
||||
RadioInterfaceService.getBondedDeviceAddress(requireContext()) != null
|
||||
|
||||
// get rid of the warning text once at least one device is paired
|
||||
warningNotPaired.visibility = if (hasBonded) View.GONE else View.VISIBLE
|
||||
|
@ -571,7 +582,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
deviceRadioGroup.visibility = View.GONE
|
||||
changeRadioButton.visibility = View.VISIBLE
|
||||
|
||||
val curRadio = BluetoothInterfaceService.getBondedDeviceAddress(requireContext())
|
||||
val curRadio = RadioInterfaceService.getBondedDeviceAddress(requireContext())
|
||||
|
||||
if (curRadio != null) {
|
||||
scanStatusText.text = getString(R.string.current_pair).format(curRadio)
|
||||
|
|
Ładowanie…
Reference in New Issue