diff --git a/TODO.md b/TODO.md index eaf0b407f..5bf41ae0e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ # Remaining tasks before declaring 1.0 +- add faq entry about range and antennas and rain - first message sent is still doubled for some people -- disable software update button after update finishes - let users set arbitrary params in android - add a low level settings screen (let user change any of the RadioConfig parameters) diff --git a/app/build.gradle b/app/build.gradle index df3d5b631..c6dbff30e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 10785 // format is Mmmss (where M is 1+the numeric major number - versionName "0.7.85" + versionCode 10787 // format is Mmmss (where M is 1+the numeric major number + versionName "0.7.87" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/debug/assets/firmware b/app/src/debug/assets/firmware new file mode 120000 index 000000000..e4a11c5bf --- /dev/null +++ b/app/src/debug/assets/firmware @@ -0,0 +1 @@ +../../../../../meshtastic-esp32/release/latest/bins \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index bf6557b8c..ca4ff5d2c 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -772,7 +772,12 @@ class MainActivity : AppCompatActivity(), Logging, if (model.meshService != null) Exceptions.reportError("meshService was supposed to be null, ignoring (but reporting a bug)") - MeshService.startService(this) // Start the service so it stays running even after we unbind + try { + MeshService.startService(this) // Start the service so it stays running even after we unbind + } catch (ex: Exception) { + // Old samsung phones have a race condition andthis might rarely fail. Which is probably find because the bind will be sufficient most of the time + errormsg("Failed to start service from activity - but ignoring because bind will work ${ex.message}") + } // ALSO bind so we can use the api mesh.connect(this, MeshService.intent, Context.BIND_AUTO_CREATE + Context.BIND_ABOVE_CLIENT) diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index e630c5c73..5a3cac3b7 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -354,35 +354,40 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String private var isFirstTime = true private fun doDiscoverServicesAndInit() { - // FIXME - no need to discover services more than once - instead use lazy() to use them in future attempts safe!!.asyncDiscoverServices { discRes -> - discRes.getOrThrow() // FIXME, instead just try to reconnect? + try { + discRes.getOrThrow() - 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 + 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 - /* if (isFirstTime) { - isFirstTime = false - throw BLEException("Faking a BLE failure") - } */ + /* if (isFirstTime) { + isFirstTime = false + throw BLEException("Faking a BLE failure") + } */ - fromNum = getCharacteristic(BTM_FROMNUM_CHARACTER) + fromNum = getCharacteristic(BTM_FROMNUM_CHARACTER) - // We treat the first send by a client as special - isFirstSend = true + // We treat the first send by a client as special + isFirstSend = true - // Now tell clients they can (finally use the api) - service.onConnect() + // Now tell clients they can (finally use the api) + service.onConnect() - // Immediately broadcast any queued packets sitting on the device - doReadFromRadio(true) - } catch (ex: BLEException) { - scheduleReconnect( - "Unexpected error in initial device enumeration, forcing disconnect $ex" - ) + // Immediately broadcast any queued packets sitting on the device + doReadFromRadio(true) + } catch (ex: BLEException) { + scheduleReconnect( + "Unexpected error in initial device enumeration, forcing disconnect $ex" + ) + } } + } catch (ex: BLEException) { + scheduleReconnect( + "Unexpected error discovering services, forcing disconnect $ex" + ) } } } diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 28c0b8731..51d30f5f4 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1461,6 +1461,8 @@ class MeshService : Service(), Logging { errormsg("Unable to update", ex) null } + + debug("setFirmwareUpdateFilename $firmwareUpdateFilename") } private fun doFirmwareUpdate() { diff --git a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt index ec3bf6dee..e894c9d7e 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -85,8 +85,11 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } override fun toString(): String { - return super.toString() + return "Work:$tag" } + + /// Connection work items are treated specially + fun isConnect() = tag == "connect" || tag == "reconnect" } /** @@ -122,6 +125,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private val STATUS_RELIABLE_WRITE_FAILED = 4403 private val STATUS_TIMEOUT = 4404 private val STATUS_NOSTART = 4405 + private val STATUS_SIMFAILURE = 4406 private val gattCallback = object : BluetoothGattCallback() { @@ -157,7 +161,11 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD if (oldstate == BluetoothProfile.STATE_CONNECTED) { info("Lost connection - aborting current work: $currentWork") - dropAndReconnect() + // If we get a disconnect, just try again otherwise fail all current operations + if (currentWork?.isConnect() == true) + dropAndReconnect() + else + lostConnection("lost connection") } else if (status == 133) { // We were not previously connected and we just failed with our non-auto connection attempt. Therefore we now need // to do an autoconnection attempt. When that attempt succeeds/fails the normal callbacks will be called @@ -285,6 +293,12 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } } + // To test loss of BLE faults we can randomly fail a certain % of all work items. We + // skip this for "connect" items because the handling for connection failure is special + var simFailures = false + var failPercent = + 10 // 15% failure is unusably high because of constant reconnects, 7% somewhat usable, 10% pretty bad + private val failRandom = Random() private var activeTimeout: Job? = null @@ -311,13 +325,22 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD isSettingMtu = false // Most work is not doing MTU stuff, the work that is will re set this flag - val started = newWork.startWork() - if (!started) { - errormsg("Failed to start work, returned error status") - completeWork( - STATUS_NOSTART, - Unit - ) // abandon the current attempt and try for another + + val failThis = + simFailures && !newWork.isConnect() && failRandom.nextInt(100) < failPercent + + if (failThis) { + errormsg("Simulating random work failure!") + completeWork(STATUS_SIMFAILURE, Unit) + } else { + val started = newWork.startWork() + if (!started) { + errormsg("Failed to start work, returned error status") + completeWork( + STATUS_NOSTART, + Unit + ) // abandon the current attempt and try for another + } } } } @@ -499,8 +522,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } } - /// Drop our current connection and then requeue a connect as needed - private fun dropAndReconnect() { + private fun lostConnection(reason: String) { /* Supposedly this reconnect attempt happens automatically "If the connection was established through an auto connect, Android will @@ -511,7 +533,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD closeConnection() */ - failAllWork(BLEException("Lost connection")) + failAllWork(BLEException(reason)) // Cancel any notifications - because when the device comes back it might have forgotten about us notifyHandlers.clear() @@ -520,6 +542,11 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD debug("calling lostConnect handler") it.invoke() } + } + + /// Drop our current connection and then requeue a connect as needed + private fun dropAndReconnect() { + lostConnection("lost connection, reconnecting") // Queue a new connection attempt val cb = connectionCallback diff --git a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt index 38b31b0a6..d1f6957f1 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -230,10 +230,12 @@ class SoftwareUpdateService : JobIntentService(), Logging { val (region) = regionRegex.find(hwVer)?.destructured ?: throw Exception("Malformed hw version") - val name = "firmware/firmware-$mfg-$region-$curver.bin" + val base = "firmware-$mfg-$region-$curver.bin" + // Check to see if the file exists (some builds might not include update files for size reasons) - return if (!context.assets.list(name).isNullOrEmpty()) - name + val firmwareFiles = context.assets.list("firmware") ?: arrayOf() + return if (firmwareFiles.contains(base)) + "firmware/$base" else null } 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 956ec7bde..5932afa0e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -484,10 +484,12 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { updateProgressBar.progress = service.updateStatus delay(2000) // Only check occasionally } + + val isSuccess = (service.updateStatus == -1) scanStatusText.text = - if (service.updateStatus == -1) "Update successful" else "Update failed" + if (isSuccess) "Update successful" else "Update failed" updateProgressBar.isEnabled = false - updateFirmwareButton.isEnabled = true + updateFirmwareButton.isEnabled = !isSuccess } } } @@ -504,7 +506,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // If actively connected possibly let the user update firmware val info = model.myNodeInfo.value - if (connected == MeshService.ConnectionState.CONNECTED && info != null && info.shouldUpdate) { + if (connected == MeshService.ConnectionState.CONNECTED && info != null && info.shouldUpdate && info.couldUpdate) { updateFirmwareButton.visibility = View.VISIBLE updateFirmwareButton.text = getString(R.string.update_to).format(getString(R.string.cur_firmware_version)) diff --git a/geeksville-androidlib b/geeksville-androidlib index 10d3b2e62..95c5a9aa9 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit 10d3b2e62b26d41b9b2568eefb3c207442c1cc5c +Subproject commit 95c5a9aa950f917857a3cc0c7cd84a4a56993032