mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
commit
5d6c86f943
8 changed files with 123 additions and 91 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue