Merge pull request #148 from geeksville/dev

misc bug fixing
This commit is contained in:
Kevin Hester 2020-08-30 12:27:23 -07:00 committed by GitHub
commit 5d6c86f943
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 123 additions and 91 deletions

View file

@ -23,13 +23,13 @@ android {
}
} */
compileSdkVersion 29
buildToolsVersion "30.0.2"
buildToolsVersion "30.0.1" // Note: 30.0.0.2 doesn't yet work on Github actions CI
defaultConfig {
applicationId "com.geeksville.mesh"
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
targetSdkVersion 29
versionCode 10905 // format is Mmmss (where M is 1+the numeric major number
versionName "0.9.05"
versionCode 10906 // format is Mmmss (where M is 1+the numeric major number
versionName "0.9.06"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@ -112,15 +112,15 @@ dependencies {
implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'com.google.android.material:material:1.2.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// kotlin serialization
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:1.0-M1-1.4.0-rc"

View file

@ -4,6 +4,7 @@ import android.graphics.Bitmap
import android.net.Uri
import android.util.Base64
import com.geeksville.mesh.MeshProtos
import com.geeksville.util.anonymize
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder
@ -32,15 +33,18 @@ data class Channel(
.setModemConfig(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128).build()
)
const val prefix = "https://www.meshtastic.org/c/#"
private const val prefixRoot = "https://www.meshtastic.org/c/"
const val prefix = "$prefixRoot#"
private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP
private fun urlToSettings(url: Uri): MeshProtos.ChannelSettings {
val urlStr = url.toString()
val pathRegex = Regex("$prefix(.*)")
// Let the path optionally include the # character (or not) so we can work with old URLs generated by old versions of the app
val pathRegex = Regex("$prefixRoot#?(.*)")
val (base64) = pathRegex.find(urlStr)?.destructured
?: throw MalformedURLException("Not a meshtastic URL")
?: throw MalformedURLException("Not a meshtastic URL: ${urlStr.anonymize(40)}")
val bytes = Base64.decode(base64, base64Flags)
return MeshProtos.ChannelSettings.parseFrom(bytes)

View file

@ -409,9 +409,12 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
}
}
} catch (ex: BLEException) {
scheduleReconnect(
"Unexpected error discovering services, forcing disconnect $ex"
)
if (s.gatt == null)
warn("GATT was closed while discovering, assume we are shutting down")
else
scheduleReconnect(
"Unexpected error discovering services, forcing disconnect $ex"
)
}
}
}

View file

@ -29,6 +29,7 @@ import com.geeksville.mesh.MeshProtos.MeshPacket
import com.geeksville.mesh.MeshProtos.ToRadio
import com.geeksville.mesh.R
import com.geeksville.util.*
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.*
import com.google.protobuf.ByteString
@ -262,20 +263,30 @@ class MeshService : Service(), Logging {
locationSettingsResponse.addOnFailureListener { exception ->
errormsg("Failed to listen to GPS")
if (exception is ResolvableApiException) {
exceptionReporter {
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
// exception.startResolutionForResult(this@MainActivity, REQUEST_CHECK_SETTINGS)
when (exception) {
is ResolvableApiException ->
exceptionReporter {
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
// For now just punt and show a dialog
warnUserAboutLocation()
}
} else
Exceptions.report(exception)
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
// exception.startResolutionForResult(this@MainActivity, REQUEST_CHECK_SETTINGS)
// For now just punt and show a dialog
warnUserAboutLocation()
}
is ApiException ->
if (exception.statusCode == 17) {
// error: cancelled by user
errormsg("User cancelled location access", exception)
} else {
Exceptions.report(exception)
}
else ->
Exceptions.report(exception)
}
}
val client = LocationServices.getFusedLocationProviderClient(this)

View file

@ -32,8 +32,8 @@ fun longBLEUUID(hexFour: String): UUID = UUID.fromString("0000$hexFour-0000-1000
class SafeBluetooth(private val context: Context, private val device: BluetoothDevice) :
Logging, Closeable {
/// Timeout before we declare a bluetooth operation failed
var timeoutMsec = 15 * 1000L
/// Timeout before we declare a bluetooth operation failed (used for synchronous API operations only)
var timeoutMsec = 20 * 1000L
/// Users can access the GATT directly as needed
@Volatile
@ -361,7 +361,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
private fun <T> queueWork(
tag: String,
cont: Continuation<T>,
timeout: Long = 0,
timeout: Long,
initFn: () -> Boolean
) {
val btCont =
@ -455,7 +455,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
private fun <T> makeSync(wrappedFn: (SyncContinuation<T>) -> Unit): T {
val cont = SyncContinuation<T>()
wrappedFn(cont)
return cont.await(timeoutMsec)
return cont.await() // was timeoutMsec but we now do the timeout at the lower BLE level
}
// Is the gatt trying to repeatedly connect as needed?
@ -495,12 +495,13 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
// Otherwise if you pass in false, it will try to connect now and will timeout and fail in 30 seconds.
private fun queueConnect(
autoConnect: Boolean = false,
cont: Continuation<Unit>
cont: Continuation<Unit>,
timeout: Long = 0
) {
this.autoConnect = autoConnect
// assert(gatt == null) this now might be !null with our new reconnect support
queueWork("connect", cont) {
queueWork("connect", cont, timeout) {
// Note: To workaround https://issuetracker.google.com/issues/36995652
// Always call BluetoothDevice#connectGatt() with autoConnect=false
@ -585,7 +586,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
}
// note - we don't need an init fn (because that would normally redo the connectGatt call - which we don't need)
queueWork("reconnect", CallbackContinuation(cb)) { -> true }
queueWork("reconnect", CallbackContinuation(cb), 0) { -> true }
} else {
debug("No connectionCallback registered")
}
@ -596,20 +597,24 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
private fun queueReadCharacteristic(
c: BluetoothGattCharacteristic,
cont: Continuation<BluetoothGattCharacteristic>
) = queueWork("readC ${c.uuid}", cont) { gatt!!.readCharacteristic(c) }
cont: Continuation<BluetoothGattCharacteristic>, timeout: Long = 0
) = queueWork("readC ${c.uuid}", cont, timeout) { gatt!!.readCharacteristic(c) }
fun asyncReadCharacteristic(
c: BluetoothGattCharacteristic,
cb: (Result<BluetoothGattCharacteristic>) -> Unit
) = queueReadCharacteristic(c, CallbackContinuation(cb))
fun readCharacteristic(c: BluetoothGattCharacteristic): BluetoothGattCharacteristic =
makeSync { queueReadCharacteristic(c, it) }
fun readCharacteristic(
c: BluetoothGattCharacteristic,
timeout: Long = timeoutMsec
): BluetoothGattCharacteristic =
makeSync { queueReadCharacteristic(c, it, timeout) }
private fun queueDiscoverServices(cont: Continuation<Unit>) {
queueWork("discover", cont) {
gatt?.discoverServices() ?: throw BLEException("GATT is null")
private fun queueDiscoverServices(cont: Continuation<Unit>, timeout: Long = 0) {
queueWork("discover", cont, timeout) {
gatt?.discoverServices()
?: false // throw BLEException("GATT is null") - if we return false here it is probably because the device is being torn down
}
}
@ -649,8 +654,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
private fun queueWriteCharacteristic(
c: BluetoothGattCharacteristic,
v: ByteArray,
cont: Continuation<BluetoothGattCharacteristic>
) = queueWork("writeC ${c.uuid}", cont) {
cont: Continuation<BluetoothGattCharacteristic>, timeout: Long = 0
) = queueWork("writeC ${c.uuid}", cont, timeout) {
currentReliableWrite = null
c.value = v
gatt?.writeCharacteristic(c) ?: false
@ -664,17 +669,18 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
fun writeCharacteristic(
c: BluetoothGattCharacteristic,
v: ByteArray
v: ByteArray,
timeout: Long = timeoutMsec
): BluetoothGattCharacteristic =
makeSync { queueWriteCharacteristic(c, v, it) }
makeSync { queueWriteCharacteristic(c, v, it, timeout) }
/** Like write, but we use the extra reliable flow documented here:
* https://stackoverflow.com/questions/24485536/what-is-reliable-write-in-ble
*/
private fun queueWriteReliable(
c: BluetoothGattCharacteristic,
cont: Continuation<Unit>
) = queueWork("rwriteC ${c.uuid}", cont) {
cont: Continuation<Unit>, timeout: Long = 0
) = queueWork("rwriteC ${c.uuid}", cont, timeout) {
logAssert(gatt!!.beginReliableWrite())
currentReliableWrite = c.value.clone()
gatt?.writeCharacteristic(c) ?: false
@ -690,8 +696,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
private fun queueWriteDescriptor(
c: BluetoothGattDescriptor,
cont: Continuation<BluetoothGattDescriptor>
) = queueWork("writeD", cont) { gatt?.writeDescriptor(c) ?: false }
cont: Continuation<BluetoothGattDescriptor>, timeout: Long = 0
) = queueWork("writeD", cont, timeout) { gatt?.writeDescriptor(c) ?: false }
fun asyncWriteDescriptor(
c: BluetoothGattDescriptor,
@ -765,7 +771,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
// Cancel any notifications - because when the device comes back it might have forgotten about us
notifyHandlers.clear()
closeGatt()
failAllWork(BLEException("Connection closing"))

View file

@ -205,7 +205,6 @@ class SoftwareUpdateService : JobIntentService(), Logging {
verStringToInt(if (swVer.isEmpty() || swVer == "unset") "99.99.99" else swVer)
(curVer > deviceVersion) && (deviceVersion >= minVer)
// true
} catch (ex: Exception) {
errormsg("Error finding swupdate info", ex)
false // If we fail parsing our update info
@ -313,25 +312,30 @@ class SoftwareUpdateService : JobIntentService(), Logging {
firmwareNumSent += blockSize
}
// We have finished sending all our blocks, so post the CRC so our state machine can advance
val c = firmwareCrc.value
info("Sent all blocks, crc is $c")
sync.writeCharacteristic(
crc32Desc,
toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32)
)
try {
// We have finished sending all our blocks, so post the CRC so our state machine can advance
val c = firmwareCrc.value
info("Sent all blocks, crc is $c")
sync.writeCharacteristic(
crc32Desc,
toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32)
)
// we just read the update result if !0 we have an error
val updateResult =
sync.readCharacteristic(updateResultDesc)
.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0)
if (updateResult != 0) {
progress = -2
throw Exception("Device update failed, reason=$updateResult")
// we just read the update result if !0 we have an error
val updateResult =
sync.readCharacteristic(updateResultDesc)
.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0)
if (updateResult != 0) {
progress = -2
throw Exception("Device update failed, reason=$updateResult")
}
// Device will now reboot
} catch (ex: BLEException) {
// We might get SyncContinuation timeout on the final write, assume the device simply rebooted to run the new load and we missed it
errormsg("Assuming successful update", ex)
}
// Device will now reboot
progress = -1 // success
}
} catch (ex: BLEException) {

View file

@ -31,11 +31,13 @@
<ImageView
android:id="@+id/qrView"
android:layout_width="0dp"
android:layout_height="200dp"
android:layout_marginStart="96dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="96dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:contentDescription="@string/qr_code"
app:layout_constraintBottom_toTopOf="@+id/channelOptions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/channelNameView"
@ -73,15 +75,37 @@
app:icon="@drawable/ic_twotone_lock_open_24" />
</com.google.android.material.button.MaterialButtonToggleGroup>
-->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/channelOptions"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:layout_marginEnd="64dp"
android:layout_marginBottom="16dp"
android:hint="@string/channel_options"
app:layout_constraintBottom_toTopOf="@+id/editableCheckbox"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<AutoCompleteTextView
android:id="@+id/filled_exposed_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/channel_options" />
</com.google.android.material.textfield.TextInputLayout>
<CheckBox
android:id="@+id/editableCheckbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="96dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:button="@drawable/sl_lock_24dp"
app:layout_constraintTop_toBottomOf="@+id/channelOptions"></CheckBox>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/shareButton"
app:layout_constraintStart_toStartOf="parent"></CheckBox>
<ImageButton
android:id="@+id/shareButton"
@ -94,24 +118,4 @@
app:layout_constraintTop_toTopOf="@+id/editableCheckbox"
app:srcCompat="@drawable/ic_twotone_share_24" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/channelOptions"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="64dp"
android:hint="@string/channel_options"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/qrView">
<AutoCompleteTextView
android:id="@+id/filled_exposed_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/channel_options" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1 +1 @@
Subproject commit 629a5b1621d1e79772ddf00290da1edec3a74e10
Subproject commit 775502e999b06c1e8effbfd631b1924d998b0570