diff --git a/app/src/main/java/com/geeksville/mesh/service/IRadioInterface.kt b/app/src/main/java/com/geeksville/mesh/service/IRadioInterface.kt index 32beaade7..c8d5f1b7a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/IRadioInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/IRadioInterface.kt @@ -4,4 +4,13 @@ import java.io.Closeable interface IRadioInterface : Closeable { fun handleSendToRadio(p: ByteArray) +} + +class NopInterface : IRadioInterface { + override fun handleSendToRadio(p: ByteArray) { + } + + override fun close() { + } + } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index da2e62d54..c80cc1b76 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -105,6 +105,10 @@ class RadioInterfaceService : Service(), Logging { private val serviceJob = Job() val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob) + private val nopIf = NopInterface() + private var radioIf: IRadioInterface = nopIf + + /** * If the user turns on bluetooth after we start, make sure to try and reconnected then */ @@ -123,9 +127,7 @@ class RadioInterfaceService : Service(), Logging { /// 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) - } + radioIf.handleSendToRadio(p) } // Handle an incoming packet from the radio, broadcasts it as an android intent @@ -166,11 +168,9 @@ class RadioInterfaceService : Service(), Logging { } - private var radioIf: IRadioInterface? = null - /** Start our configured interface (if it isn't already running) */ private fun startInterface() { - if (radioIf != null) + if (radioIf != nopIf) warn("Can't start interface - $radioIf is already running") else { val address = getBondedDeviceAddress(this) @@ -189,6 +189,7 @@ class RadioInterfaceService : Service(), Logging { radioIf = when (c) { 'x' -> BluetoothInterface(this, rest) 's' -> SerialInterface(this, rest) + 'n' -> nopIf else -> TODO("Unexpected radio interface type") } } @@ -197,23 +198,24 @@ class RadioInterfaceService : Service(), Logging { private fun stopInterface() { - info("stopping interface $radioIf") - radioIf?.let { r -> - radioIf = null - r.close() + val r = radioIf + info("stopping interface $r") + radioIf = nopIf + r.close() - if (logSends) - sentPacketsLog.close() - if (logReceives) - receivedPacketsLog.close() + if (logSends) + sentPacketsLog.close() + if (logReceives) + receivedPacketsLog.close() + // Don't broadcast disconnects if we were just using the nop device + if (radioIf != nopIf) onDisconnect(isPermanent = true) // Tell any clients we are now offline - } } @SuppressLint("NewApi") - fun setBondedDeviceAddress(addr: String?) { + fun setBondedDeviceAddress(addressIn: String?) { // Record that this use has configured a radio GeeksvilleApplication.analytics.track( "mesh_bond" @@ -224,15 +226,18 @@ class RadioInterfaceService : Service(), Logging { stopInterface() } - debug("Setting bonded device to $addr") + // The device address "n" can be used to mean none + val address = if ("n" == addressIn) null else addressIn + + debug("Setting bonded device to $address") getPrefs(this).edit(commit = true) { this.remove(DEVADDR_KEY_OLD) // remove any old version of the key - if (addr == null) + if (address == null) this.remove(DEVADDR_KEY) else - putString(DEVADDR_KEY, addr) + putString(DEVADDR_KEY, address) } // Force the service to reconnect diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 3c9c53ca2..038c3163a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -99,11 +99,11 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { debug("BTScanModel created") } - data class BTScanEntry(val name: String, val address: String, val bonded: Boolean) { + data class DeviceListEntry(val name: String, val address: String, val bonded: Boolean) { // val isSelected get() = macAddress == selectedMacAddr override fun toString(): String { - return "BTScanEntry(name=${name.anonymize}, addr=${address.anonymize})" + return "DeviceListEntry(name=${name.anonymize}, addr=${address.anonymize})" } } @@ -131,6 +131,8 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { null } + /// Use the string for the NopInterface + val selectedNotNull: String get() = selectedAddress ?: "n" private val scanCallback = object : ScanCallback() { override fun onScanFailed(errorCode: Int) { @@ -145,31 +147,37 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { override fun onScanResult(callbackType: Int, result: ScanResult) { val addr = result.device.address + val fullAddr = "x$addr" // full address with the bluetooh prefix // prevent logspam because weill get get lots of redundant scan results val isBonded = result.device.bondState == BluetoothDevice.BOND_BONDED val oldDevs = devices.value!! - val oldEntry = oldDevs[addr] + val oldEntry = oldDevs[fullAddr] if (oldEntry == null || oldEntry.bonded != isBonded) { - val entry = BTScanEntry( + val entry = DeviceListEntry( result.device.name ?: "unnamed-$addr", // autobug: some devices might not have a name, if someone is running really old device code? - "x$addr", + fullAddr, isBonded ) debug("onScanResult ${entry}") - // If nothing was selected, by default select the first thing we see + // If nothing was selected, by default select the first valid thing we see if (selectedAddress == null && entry.bonded) changeScanSelection( GeeksvilleApplication.currentActivity as MainActivity, addr ) - oldDevs[addr] = entry // Add/replace entry - devices.value = oldDevs // trigger gui updates + addDevice(entry) // Add/replace entry } } } + private fun addDevice(entry: DeviceListEntry) { + val oldDevs = devices.value!! + oldDevs[entry.address] = entry // Add/replace entry + devices.value = oldDevs // trigger gui updates + } + fun stopScan() { if (scanner != null) { debug("stopping scan") @@ -193,8 +201,8 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { warn("No bluetooth adapter. Running under emulation?") val testnodes = listOf( - BTScanEntry("Meshtastic_ab12", "xaa", false), - BTScanEntry("Meshtastic_32ac", "xbb", true) + DeviceListEntry("Meshtastic_ab12", "xaa", false), + DeviceListEntry("Meshtastic_32ac", "xbb", true) ) devices.value = (testnodes.map { it.address to it }).toMap().toMutableMap() @@ -220,6 +228,9 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { if (scanner == null) { debug("starting scan") + // Include a placeholder for "None" + addDevice(DeviceListEntry(context.getString(R.string.none), "n", true)) + // filter and only accept devices that have a sw update service val filter = ScanFilter.Builder() @@ -240,7 +251,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { } } - val devices = object : MutableLiveData>(mutableMapOf()) { + val devices = object : MutableLiveData>(mutableMapOf()) { /** @@ -262,7 +273,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { /// Called by the GUI when a new device has been selected by the user /// Returns true if we were able to change to that item - fun onSelected(activity: MainActivity, it: BTScanEntry): Boolean { + fun onSelected(activity: MainActivity, it: DeviceListEntry): Boolean { // If the device is paired, let user select it, otherwise start the pairing flow if (it.bonded) { changeScanSelection(activity, it.address) @@ -293,7 +304,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}") + info("Changing device to ${newAddr.anonymize}") selectedAddress = newAddr changeDeviceSelection(context, newAddr) } @@ -437,12 +448,12 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - private fun addDeviceButton(device: BTScanModel.BTScanEntry, enabled: Boolean) { + private fun addDeviceButton(device: BTScanModel.DeviceListEntry, enabled: Boolean) { val b = RadioButton(requireActivity()) b.text = device.name b.id = View.generateViewId() b.isEnabled = enabled - b.isChecked = device.address == scanModel.selectedAddress + b.isChecked = device.address == scanModel.selectedNotNull deviceRadioGroup.addView(b) // Once we have at least one device, don't show the "looking for" animation - it makes uers think @@ -496,20 +507,21 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { var hasShownOurDevice = false devices.values.forEach { device -> - hasShownOurDevice = - hasShownOurDevice || device.address == scanModel.selectedAddress + if (device.address == scanModel.selectedNotNull) + hasShownOurDevice = true 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.selectedBluetooth + val selectedAddr = scanModel.selectedAddress if (!hasShownOurDevice && selectedAddr != null) { - val bDevice = scanModel.bluetoothAdapter!!.getRemoteDevice(selectedAddr) + val bDevice = + scanModel.bluetoothAdapter!!.getRemoteDevice(scanModel.selectedBluetooth) if (bDevice.name != null) { // ignore nodes that node have a name, that means we've lost them since they appeared - val curDevice = BTScanModel.BTScanEntry( + val curDevice = BTScanModel.DeviceListEntry( bDevice.name, - bDevice.address, + selectedAddr, bDevice.bondState == BOND_BONDED ) addDeviceButton(curDevice, false) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 900780f9d..bd90d6a0c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,4 +61,5 @@ Update to %s Application too old You must update this application on the Google Play store (or Github). It is too old to talk to this radio. + None